Compare commits

..

297 Commits

Author SHA1 Message Date
otto 2320982887 feat(新闻资讯): 获取热点文章时,展示文章头图 2026-07-10 11:35:21 +08:00
otto 74f9063e4a feat(软件工厂专区): 创建文章时新增内部参数publishDate,内部调用该接口时可通过修改publishDate来设置文章的发布、创建、更新时间 2026-07-09 10:47:47 +08:00
xxq250 bc0aed63c9 fixed 成果字典数据统计 2026-07-08 16:45:32 +08:00
xxq250 20b7e93bfb fixed 成果数据对接的用户信息 2026-07-08 15:39:24 +08:00
xxq250 9e637de0c5 fixed 成果数据用户识别 2026-07-07 17:10:48 +08:00
xxq250 e21dd46aaa fixed 成果数据用户导入文案 2026-07-07 16:58:30 +08:00
xxq250 d2a7a74ef6 fixed 成果数据用户导入文案 2026-07-07 16:51:46 +08:00
xxq250 4485d8b5a2 fixed 成果数据用户隔离 2026-07-07 16:10:40 +08:00
xxq250 d9a5821cc5 fixed 仅统计上架成果 2026-07-07 10:47:45 +08:00
xxq250 f91685320b 文案成果领域一级二级 2026-07-07 10:13:59 +08:00
xxq250 fc420f7b8e 成果记录list查询 2026-07-07 09:14:10 +08:00
xxq250 d4dac70695 成果记录list查询 2026-07-07 09:10:16 +08:00
xxq250 8670953e8a 成果记录list查询 2026-07-06 17:43:02 +08:00
xxq250 7ea1f7fde5 成果记录open查询 2026-07-06 17:04:21 +08:00
xxq250 ee48cf7547 成果记录查询sql 2026-07-06 16:35:34 +08:00
xxq250 823adf4853 成果用户个人记录log 2026-07-06 09:00:49 +08:00
xxq250 7e3be6366f 成果用户个人记录log 2026-07-06 08:50:08 +08:00
xxq250 4264a4f1ad 成果用户个人记录sql 2026-07-06 08:40:19 +08:00
xxq250 815b500947 成果用户个人记录 2026-07-03 17:17:03 +08:00
xxq250 986c522f7c 成果用户id记录和管理状态 2026-07-03 17:08:57 +08:00
xxq250 68b958208c 成果用户id记录和管理状态 2026-07-03 17:03:14 +08:00
xxq250 2d142a428f open成果的相关行为数 2026-07-03 16:18:26 +08:00
xxq250 ea9d0ff37c 查询增加同步 2026-07-03 15:29:10 +08:00
xxq250 3133fdce15 reset file 2026-07-03 11:15:22 +08:00
xxq250 c10f49d209 fixed 成果库首页新接口 2026-07-03 11:14:13 +08:00
xxq250 9e49f23a70 fixed 后台显示附件名称 2026-07-01 10:14:13 +08:00
xxq250 1fc41ead3b fixed 后台显示附件入驻承诺书加到成果 2026-07-01 09:48:52 +08:00
xxq250 1155e4a4b8 fixed 附件入驻承诺书加到成果 2026-07-01 09:19:05 +08:00
xxq250 3b13534717 成果库改版 2026-06-30 16:22:23 +08:00
xxq250 5ed80f2769 无人机专区查询成果库sql 2026-06-26 09:16:40 +08:00
xxq250 bd153dec44 成果全部支持领域查询 2026-06-17 15:38:43 +08:00
xxq250 8601e91125 成果导入增加封面图 2026-06-16 09:39:54 +08:00
xxq250 7e29c5b8de 成果封面图不转换 2026-06-16 09:28:00 +08:00
xxq250 998dad05e7 成果封面图sql 2026-06-16 09:15:29 +08:00
xxq250 f3ba40e400 成果封面图sql 2026-06-16 09:10:59 +08:00
xxq250 be61b61b85 成果封面图 2026-06-16 09:05:47 +08:00
xxq250 c54c31d711 成果技术领域 2026-06-05 17:28:36 +08:00
xxq250 5508a34a51 Merge remote-tracking branch 'origin/dev_monitoring' into dev_monitoring 2026-06-05 16:57:48 +08:00
xxq250 c939148c75 打包目录处理 2026-06-05 16:57:37 +08:00
wanjia9506 0eaf82e3c7 Merge pull request '创客统计中去除二次、三次发布的任务' (#1124) from wanjia9506/microservices:dev_monitoring into dev_monitoring 2026-05-18 15:04:30 +08:00
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
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 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
361 changed files with 9298 additions and 14414 deletions

View File

@ -9,8 +9,6 @@ import com.microservices.system.api.factory.RemoteCmsFallbackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 用户服务
*
@ -160,22 +158,4 @@ public interface RemoteCmsService {
@GetMapping("/doc/getJSONObject/{cmsDocId}")
R<JSONObject> selectCmsDocJSONObjectById(@PathVariable(value = "cmsDocId") Long cmsDocId
, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
@PutMapping("/project/changeOrgRole/user/{userId}/operate/{operate}")
R<Boolean> changeUserOrgRole(@PathVariable("userId") Long userId, @PathVariable("operate") int operate
, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
/**
* 首页文章合并列表接收置顶文章 docId 有序列表返回合并去重后的结果
* Zone 服务 /open/getTopDoc 改造后调用
*
* @param zoneId 专区ID
* @param topDocIds 置顶文章 docId 有序列表
* @param source 来源
* @return 合并后的首页文章 JSONObject 列表
*/
@PostMapping("/doc/homePageDocMergeList/{zoneId}")
R<List<JSONObject>> selectHomePageDocMergeList(@PathVariable("zoneId") Long zoneId,
@RequestBody List<Long> topDocIds,
@RequestHeader(SecurityConstants.FROM_SOURCE) String source);
}

View File

@ -85,7 +85,7 @@ public interface RemoteDeptService {
* 根据组织id删除部门
*
* @param deptId 部门id
* @param source 请求来源
* @param source 请求来源
* @return 是否删除成功
*/
@DeleteMapping("/dept/deleteDeptByDeptId")
@ -105,7 +105,7 @@ public interface RemoteDeptService {
* 通过用户名查询用户下组织及组织对应角色列表
*
* @param deptId 组织ID
* @param source 请求来源
* @param source 请求来源
* @return 用户下组织及组织对应角色列表
*/
@GetMapping("/SysUserDeptRole/userByDeptId/{deptId}/list")
@ -115,7 +115,7 @@ public interface RemoteDeptService {
/**
* 查询组织编号下拥有传入角色权限字符串的用户名
*
* @param deptId 组织编号
* @param deptId 组织编号
* @param roleKeys 角色权限字符串
* @return 组织下拥有传入角色权限字符串的用户名列表
*/
@ -126,7 +126,7 @@ public interface RemoteDeptService {
* 检查用户是否在指定部门下具有指定角色
*
* @param currentDeptId 组织编号
* @param roleKey 角色权限字符串
* @param roleKey 角色权限字符串
* @return 组织下拥有传入角色权限字符串的用户名列表
*/
@GetMapping("/SysUserDeptRole/checkUserInDeptHasRole")
@ -143,17 +143,6 @@ public interface RemoteDeptService {
@GetMapping("/SysUserDeptRole/checkUserInDeptHasPerms")
R<Boolean> checkUserInDeptHasPerms(@RequestParam("currentDeptId") Long currentDeptId, @RequestParam("userId") Long userId, @RequestParam("perms") String perms, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
/**
* 检查用户是否在指定部门或指定部门的父级部门下具有指定菜单权限
*
* @param deptId 指定组织id
* @param userId 用户id
* @param perms 菜单权限
* @return 用户是否在指定部门下具有菜单权限 是true 否false
*/
@GetMapping("/SysUserDeptRole/checkUserInDeptOrParentDeptHasPerms")
R<Boolean> checkUserInDeptOrParentDeptHasPerms(@RequestParam("deptId") Long deptId, @RequestParam("userId") Long userId, @RequestParam("perms") String perms, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
/**
* 检查用户是否在指定部门下
*
@ -163,7 +152,4 @@ public interface RemoteDeptService {
*/
@GetMapping("/SysUserDeptRole/checkUserInDept")
R<Boolean> checkUserInDept(@RequestParam("currentDeptId") Long currentDeptId, @RequestParam("userId") Long userId, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
@GetMapping("/SysUserDeptRole/getSysUserDeptRoleByDeptIdAndUserId")
R<SysUserDeptRole> getSysUserDeptRoleByDeptIdAndUserId(@RequestParam("deptId") Long deptId, @RequestParam("userId") Long userId, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
}

View File

@ -0,0 +1,26 @@
package com.microservices.system.api;
import com.microservices.common.core.constant.ServiceNameConstants;
import com.microservices.common.core.domain.R;
import com.microservices.system.api.domain.SysDictData;
import com.microservices.system.api.factory.RemoteDictFallbackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import java.util.List;
/**
* 文件服务
*
* @author microservices
*/
@FeignClient(contextId = "remoteDictDataService", value = ServiceNameConstants.SYSTEM_SERVICE, fallbackFactory = RemoteDictFallbackFactory.class)
public interface RemoteDictDataService {
/**
* 根据字典类型查询字典数据信息
*/
@GetMapping(value = "/dict/data/type/{dictType}")
R<List<SysDictData>> dictType(@PathVariable("dictType") String dictType);
}

View File

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

View File

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

View File

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

View File

@ -57,15 +57,6 @@ public interface RemoteUserService {
@GetMapping("/user/getSysUserByUserId/{userId}")
public R<SysUser> getSysUserByUserId(@PathVariable("userId") Long userId, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
/**
* 通过用户ID数组批量获取用户信息POST方法解决URL长度限制
*
* @param userIds 用户ID数组
* @param source 请求来源
* @return 结果
*/
@PostMapping("/user/getSysUserListByUserIds")
R<List<SysUser>> postSysUserListByUserIds(@RequestBody Long[] userIds, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
/**
* 组织下新增用户身份
@ -73,7 +64,7 @@ public interface RemoteUserService {
* @param deptId
* @param userId
* @param roleKey
* @param source 请求来源
* @param source 请求来源
* @return 结果
*/
@PostMapping("/SysUserDeptRole/dept/{deptId}/user/{userId}/role/{roleKey}")
@ -83,13 +74,14 @@ public interface RemoteUserService {
, @PathVariable("roleKey") String roleKey
, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
/**
* 组织下删除用户身份
*
* @param deptId
* @param userId
* @param roleKey
* @param source 请求来源
* @param source 请求来源
* @return 结果
*/
@DeleteMapping("/SysUserDeptRole/dept/{deptId}/user/{userId}/role/{roleKey}")
@ -99,16 +91,18 @@ public interface RemoteUserService {
@PathVariable("roleKey") String roleKey,
@RequestHeader(SecurityConstants.FROM_SOURCE) String source);
/**
* 注册用户信息
*
* @param sysUser 用户信息
* @param source 请求来源
* @param source 请求来源
* @return 结果
*/
@PostMapping("/user/register")
public R<Boolean> registerUserInfo(@RequestBody SysUser sysUser, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
/**
* 通过GitLinkToken获取本系统Token
*
@ -129,6 +123,7 @@ public interface RemoteUserService {
@GetMapping("/SysUserDeptRole/userByUserName/{userName}/list")
public R<List<SysUserDeptRole>> getSysUserDeptRoleListByUserName(@PathVariable("userName") String userName, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
/**
* 通过用户名查询用户下第一个身份
*
@ -200,7 +195,7 @@ public interface RemoteUserService {
/**
* 通过GitLink用户Id创建用户
*
* @param gitLinkUserId GitLinkUserId
* @param gitLinkUserId GitLink用户Id
* @param source 请求来源
* @return 系统用户
*/
@ -211,7 +206,7 @@ public interface RemoteUserService {
* 根据角色Key获取菜单权限集合
*
* @param roleKey 角色Key
* @param source 请求来源
* @param source 请求来源
* @return 菜单权限集合
*/
@GetMapping("/menu/roleMenuPermissionByRoleKey/{roleKey}")

View File

@ -9,8 +9,6 @@ import org.slf4j.LoggerFactory;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* 内容管理服务降级处理
*
@ -85,16 +83,6 @@ public class RemoteCmsFallbackFactory implements FallbackFactory<RemoteCmsServic
public R<JSONObject> selectCmsDocJSONObjectById(Long cmsDocId, String source) {
return R.fail("获取文章JSON对象失败:" + throwable.getMessage());
}
@Override
public R<Boolean> changeUserOrgRole(Long userId, int operate, String source) {
return R.fail("更新组织权限失败:" + throwable.getMessage());
}
@Override
public R<List<JSONObject>> selectHomePageDocMergeList(Long zoneId, List<Long> topDocIds, String source) {
return R.fail("获取首页文章合并列表失败:" + throwable.getMessage());
}
};
}
}

View File

@ -83,20 +83,10 @@ public class RemoteDeptFallbackFactory implements FallbackFactory<RemoteDeptServ
return R.fail("检查用户是否在指定部门下具有指定菜单权限失败:" + throwable.getMessage());
}
@Override
public R<Boolean> checkUserInDeptOrParentDeptHasPerms(Long deptId, Long userId, String perms, String source) {
return R.fail("检查用户是否在指定部门或指定部门的父级部门下具有指定菜单权限失败:" + throwable.getMessage());
}
@Override
public R<Boolean> checkUserInDept(Long currentDeptId, Long userId, String source) {
return R.fail("检查用户是否在指定部门下失败:" + throwable.getMessage());
}
@Override
public R<SysUserDeptRole> getSysUserDeptRoleByDeptIdAndUserId(Long deptId, Long userId, String source) {
return R.fail("获取用户在指定部门下的角色失败:" + throwable.getMessage());
}
};
}
}

View File

@ -0,0 +1,33 @@
package com.microservices.system.api.factory;
import com.microservices.common.core.domain.R;
import com.microservices.system.api.RemoteDictDataService;
import com.microservices.system.api.domain.SysDictData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* 字典服务降级处理
*
* @author microservices
*/
@Component
public class RemoteDictFallbackFactory implements FallbackFactory<RemoteDictDataService> {
private static final Logger log = LoggerFactory.getLogger(RemoteDictFallbackFactory.class);
@Override
public RemoteDictDataService create(Throwable throwable) {
log.error("字典服务调用失败:{}", throwable.getMessage());
return new RemoteDictDataService() {
@Override
public R<List<SysDictData>> dictType(String dictType) {
return R.fail("获取字典列表失败:" + throwable.getMessage());
}
};
}
}

View File

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

View File

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

View File

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

View File

@ -52,12 +52,6 @@ public class RemoteUserFallbackFactory implements FallbackFactory<RemoteUserServ
return R.fail("获取用户失败:" + throwable.getMessage());
}
@Override
public R<List<SysUser>> postSysUserListByUserIds(Long[] userIds, String source) {
return R.fail("批量获取用户失败:" + throwable.getMessage());
}
@Override
public R<Boolean> addUserIdentify(Long deptId, Long userId, String roleKey, String source) {
return R.fail("组织下新增用户身份失败:" + throwable.getMessage());

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -80,10 +80,6 @@ public class CacheConstants {
* 通过组织Id获取专区详情JSON对象
*/
public final static String ZONE_JSON_OF_DEPT_ID_KEY = "zoneJSONOfDeptId:";
/**
* 积分发放部门
*/
public final static String ZONE_POINTS_DEPT_ID_KEY = "zonePointsDeptId";
/**
* 内容管理同步缓存前缀
*/
@ -217,6 +213,11 @@ public class CacheConstants {
*/
public final static String SENTINEL_TOKEN = "sentinel_token";
/**
* Rancher Token缓存Key
*/
public final static String RANCHER_TOKEN = "rancher_token";
/**
* Nacos Token缓存Key
@ -249,18 +250,6 @@ public class CacheConstants {
return ZONE_PROJECT_SORT_BY_SCORE + "*:" + zoneId + ":*";
}
/**
* 专区统计数据缓存前缀
*/
private final static String ZONE_STATISTICS_KEY = "zone:statistics:";
/**
* 获取专区统计数据缓存key
*/
public static String getZoneStatisticsKey(Long zoneId) {
return ZONE_STATISTICS_KEY + zoneId;
}
/**
* 文件打包是否完成0代表未完成1代表已完成-1代表打包失败需要重新打包
*/

View File

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

View File

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

View File

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

View File

@ -5,7 +5,8 @@ package com.microservices.common.core.constant;
*
* @author microservices
*/
public class UserConstants {
public class UserConstants
{
/**
* 平台内系统用户的唯一标志
*/
@ -27,89 +28,55 @@ public class UserConstants {
*/
public static final String DEL_FLAG_FALSE = "0";
/**
* 正常状态
*/
/** 正常状态 */
public static final String NORMAL = "0";
/**
* 异常状态
*/
/** 异常状态 */
public static final String EXCEPTION = "1";
/**
* 用户封禁状态
*/
/** 用户封禁状态 */
public static final String USER_DISABLE = "1";
/**
* 角色封禁状态
*/
/** 角色封禁状态 */
public static final String ROLE_DISABLE = "1";
/**
* 部门正常状态
*/
/** 部门正常状态 */
public static final String DEPT_NORMAL = "0";
/**
* 部门停用状态
*/
/** 部门停用状态 */
public static final String DEPT_DISABLE = "1";
/**
* 字典正常状态
*/
/** 字典正常状态 */
public static final String DICT_NORMAL = "0";
/**
* 是否为系统默认
*/
/** 是否为系统默认(是) */
public static final String YES = "Y";
/**
* 是否菜单外链
*/
/** 是否菜单外链(是) */
public static final String YES_FRAME = "0";
/**
* 是否菜单外链
*/
/** 是否菜单外链(否) */
public static final String NO_FRAME = "1";
/**
* 菜单类型目录
*/
/** 菜单类型(目录) */
public static final String TYPE_DIR = "M";
/**
* 菜单类型菜单
*/
/** 菜单类型(菜单) */
public static final String TYPE_MENU = "C";
/**
* 菜单类型按钮
*/
/** 菜单类型(按钮) */
public static final String TYPE_BUTTON = "F";
/**
* Layout组件标识
*/
/** Layout组件标识 */
public final static String LAYOUT = "Layout";
/**
* ParentView组件标识
*/
/** ParentView组件标识 */
public final static String PARENT_VIEW = "ParentView";
/**
* InnerLink组件标识
*/
/** InnerLink组件标识 */
public final static String INNER_LINK = "InnerLink";
/**
* 校验是否唯一的返回标识
*/
/** 校验是否唯一的返回标识 */
public final static boolean UNIQUE = true;
public final static boolean NOT_UNIQUE = false;
@ -155,28 +122,4 @@ public class UserConstants {
* 项目管理-知识库管理权限
*/
public static final String PMS_PERMS_DOCS_MANAGE = "pms:docs:manage";
/**
* 文章管理-文章删除权限
*/
public static final String CMS_PERMS_DOCS_DELETE = "cms:doc:remove";
/**
* 文章管理-新增文章权限
*/
public static final String CMS_PERMS_DOCS_ADD = "cms:doc:add";
/**
* 文章管理-编辑文章权限
*/
public static final String CMS_PERMS_DOCS_EDIT = "cms:doc:edit";
/**
* 文章管理-审核文章权限
*/
public static final String CMS_PERMS_DOCS_AUDIT = "cms:doc:audit";
/**
* 专区管理-审核项目权限
*/
public static final String ZONE_PERMS_PROJECT_AUDIT = "zone:project:audit";
/**
* 专区管理-审核资源权限
*/
public static final String ZONE_PERMS_RESOURCE_AUDIT = "zone:resource:audit";
}

View File

@ -8,7 +8,7 @@ package com.microservices.common.core.enums;
public enum DocAndZoneOperation
{
OPERATION_CREATE("创建", "create"), OPERATION_UPDATE("更新", "update"),
OPERATION_DELETE("删除", "delete"), OPERATION_NON_REPO_UPDATE("非仓库更新", "nonRepoUpdate");
OPERATION_DELETE("删除", "delete");
private final String operationName;
private final String operationCode;

View File

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

View File

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

View File

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

View File

@ -1,8 +1,13 @@
package com.microservices.common.httpClient.config;
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.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.slf4j.Logger;
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.Configuration;
import javax.annotation.PreDestroy;
import java.util.concurrent.TimeUnit;
/**
* HttpClient 配置类
* 支持 HTTPS 和连接池管理包含自动连接清理机制
*
* @author admin
* @date 2017/2/6
*/
@ -20,74 +31,131 @@ public class HttpClientConfiguration {
private static final Logger logger = LoggerFactory.getLogger(HttpClientConfiguration.class);
@Value("${http.maxTotal}")
@Value("${http.maxTotal:100}")
private Integer maxTotal;
@Value("${http.defaultMaxPerRoute}")
@Value("${http.defaultMaxPerRoute:20}")
private Integer defaultMaxPerRoute;
@Value("${http.connectTimeout}")
@Value("${http.connectTimeout:10000}")
private Integer connectTimeout;
@Value("${http.connectionRequestTimeout}")
@Value("${http.connectionRequestTimeout:5000}")
private Integer connectionRequestTimeout;
@Value("${http.socketTimeout}")
@Value("${http.socketTimeout:30000}")
private Integer socketTimeout;
@Value("${http.staleConnectionCheckEnabled}")
@Value("${http.staleConnectionCheckEnabled:true}")
private boolean staleConnectionCheckEnabled;
private PoolingHttpClientConnectionManager connectionManager;
/**
* 首先实例化一个连接池管理器设置最大连接数并发连接数
* 创建支持 HTTPS 的连接池管理器
* 使用自定义的 SSL 配置来避免 "peer not authenticated" 错误
*
* @return
* @return PoolingHttpClientConnectionManager
*/
@Bean(name = "httpClientConnectionManager")
public PoolingHttpClientConnectionManager getHttpClientConnectionManager() {
PoolingHttpClientConnectionManager httpClientConnectionManager = new PoolingHttpClientConnectionManager();
//最大连接数
httpClientConnectionManager.setMaxTotal(maxTotal);
//并发数
httpClientConnectionManager.setDefaultMaxPerRoute(defaultMaxPerRoute);
return httpClientConnectionManager;
try {
// 创建 SSLConnectionSocketFactory
SSLConnectionSocketFactory sslSocketFactory = SSLContextFactory.createSSLConnectionSocketFactory();
// 注册 HTTP HTTPS SocketFactory
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
* @return
* @param httpClientConnectionManager 连接池管理器
* @return 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对象
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
httpClientBuilder.setConnectionManager(httpClientConnectionManager);
org.apache.http.impl.client.HttpClientBuilder builder = HttpClients.custom()
.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
* @return
* @param httpClientBuilder HttpClientBuilder
* @return CloseableHttpClient
*/
@Bean
public CloseableHttpClient getCloseableHttpClient(@Qualifier("httpClientBuilder") HttpClientBuilder httpClientBuilder) {
return httpClientBuilder.build();
public CloseableHttpClient getCloseableHttpClient(
@Qualifier("httpClientBuilder") org.apache.http.impl.client.HttpClientBuilder httpClientBuilder) {
CloseableHttpClient httpClient = httpClientBuilder.build();
// 启动连接池清理后台线程
startConnectionMonitorThread();
logger.info("CloseableHttpClient 创建成功");
return httpClient;
}
/**
* Builder是RequestConfig的一个内部类
* 通过RequestConfig的custom方法来获取到一个Builder对象
* 设置builder的连接信息
* 这里还可以设置proxycookieSpec等属性有需要的话可以在此设置
* 创建 RequestConfig.Builder
*
* @return
* @return RequestConfig.Builder
*/
@Bean(name = "builder")
public RequestConfig.Builder getBuilder() {
@ -99,14 +167,78 @@ public class HttpClientConfiguration {
}
/**
* 使用builder构建一个RequestConfig对象
* 创建 RequestConfig
*
* @param builder
* @return
* @param builder RequestConfig.Builder
* @return RequestConfig
*/
@Bean
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

@ -211,17 +211,6 @@ public class HttpAPIService {
return doRequest(httpDelete, headers);
}
/**
* 不带Header的get请求
*
* @param url
* @return
* @throws Exception
*/
public JSONObject doGet(String url) throws Exception {
return doGet(url, null);
}
/**
* 带Header的get请求
*

View File

@ -27,6 +27,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
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.util.GitLinkRequestHelper.RequestType.GET;
@ -79,44 +80,75 @@ public class GitLinkRequestHelper {
HashMap<String, String> headers = prepareAuthenticationHeaders(gitLinkRequestUrl);
String requestId = UUID.randomUUID().toString();
String path = gitLinkRequestUrl.getPath();
logger.info("[{}]通过HttpClient向平台发起请求请求方法为{},请求网站为:{},请求路由为:{},请求载体为:{},请求头为:{}"
logger.debug("[{}]通过HttpClient向平台发起请求请求方法为{},请求网站为:{},请求路由为:{},请求载体为:{},请求头为:{}"
, requestId
, requestType
, gitLinkUrl
, path
, object
, headers);
JSONObject result;
try {
String requestURL = EscapeUtil.removeExtraSlashOfUrl(gitLinkUrl + path.replace(" ", "%20"));
switch (requestType) {
case POST:
result = httpAPIService.doPost(requestURL, (JSONObject) object, headers);
break;
case POST_FILE:
result = httpAPIService.doPostFile(requestURL, (MultipartFile) object, headers);
break;
case DELETE:
result = httpAPIService.doDelete(requestURL, (JSONObject) object, headers);
break;
case GET:
result = httpAPIService.doGet(requestURL, headers);
break;
case PUT:
result = httpAPIService.doPut(requestURL, (JSONObject) object, headers);
break;
case PATCH:
result = httpAPIService.doPatch(requestURL, (JSONObject) object, headers);
break;
default:
throw new ServiceException("不支持该请求方式:" + requestType.name());
JSONObject result = null;
Exception lastException = null;
// 业务层重试机制针对 SSL 握手失败等偶发性错误
int maxRetries = 2;
for (int attempt = 1; attempt <= maxRetries; attempt++) {
try {
String requestURL = EscapeUtil.removeExtraSlashOfUrl(gitLinkUrl + path.replace(" ", "%20"));
switch (requestType) {
case POST:
result = httpAPIService.doPost(requestURL, (JSONObject) object, headers);
break;
case POST_FILE:
result = httpAPIService.doPostFile(requestURL, (MultipartFile) object, headers);
break;
case DELETE:
result = httpAPIService.doDelete(requestURL, (JSONObject) object, headers);
break;
case GET:
result = httpAPIService.doGet(requestURL, headers);
break;
case PUT:
result = httpAPIService.doPut(requestURL, (JSONObject) object, headers);
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) {
throw new ServiceException("请求平台时,未返回内容");
}

View File

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

View File

@ -115,7 +115,8 @@ public class AuthLogic {
* @param permission 权限字符串
* @return 用户是否具备某权限
*/
public boolean hasPermi(String permission) {
public boolean hasPermi(String permission)
{
return hasPermi(tokenService.getMenuPermissions(), permission);
}
@ -125,8 +126,10 @@ public class AuthLogic {
* @param permission 权限字符串
* @return 用户是否具备某权限
*/
public void checkPermi(String permission) {
if (!hasPermi(tokenService.getMenuPermissions(), permission)) {
public void checkPermi(String permission)
{
if (!hasPermi(tokenService.getMenuPermissions(), permission))
{
throw new NotPermissionException(permission);
}
}
@ -150,10 +153,13 @@ public class AuthLogic {
*
* @param permissions 权限列表
*/
public void checkPermiAnd(String... permissions) {
public void checkPermiAnd(String... permissions)
{
Set<String> permissionList = tokenService.getMenuPermissions();
for (String permission : permissions) {
if (!hasPermi(permissionList, permission)) {
for (String permission : permissions)
{
if (!hasPermi(permissionList, permission))
{
throw new NotPermissionException(permission);
}
}
@ -164,10 +170,13 @@ public class AuthLogic {
*
* @param permissions 权限码数组
*/
public void checkPermiOr(String... permissions) {
public void checkPermiOr(String... permissions)
{
Set<String> permissionList = tokenService.getMenuPermissions();
for (String permission : permissions) {
if (hasPermi(permissionList, permission)) {
for (String permission : permissions)
{
if (hasPermi(permissionList, permission))
{
return;
}
}
@ -182,7 +191,8 @@ public class AuthLogic {
* @param role 角色标识
* @return 用户是否具备某角色
*/
public boolean hasRole(String role) {
public boolean hasRole(String role)
{
return hasRole(tokenService.getRolePermissions(), role);
}
@ -215,10 +225,13 @@ public class AuthLogic {
*
* @param roles 角色标识数组
*/
public void checkRoleAnd(String... roles) {
public void checkRoleAnd(String... roles)
{
Set<String> roleList = tokenService.getRolePermissions();
for (String role : roles) {
if (!hasRole(roleList, role)) {
for (String role : roles)
{
if (!hasRole(roleList, role))
{
throw new NotRoleException(role);
}
}
@ -229,14 +242,18 @@ public class AuthLogic {
*
* @param roles 角色标识数组
*/
public void checkRoleOr(String... roles) {
public void checkRoleOr(String... roles)
{
Set<String> roleList = tokenService.getRolePermissions();
for (String role : roles) {
if (hasRole(roleList, role)) {
for (String role : roles)
{
if (hasRole(roleList, role))
{
return;
}
}
if (roles.length > 0) {
if (roles.length > 0)
{
throw new NotRoleException(roles);
}
}
@ -306,9 +323,6 @@ public class AuthLogic {
public boolean hasUserIdentity(Long deptId, String roleKey) {
LoginUser loginUser = getLoginUser();
List<SysUserDeptRole> sysUserDeptRoleList = tokenService.getAllUserIdentity(loginUser.getUsername());
if (sysUserDeptRoleList == null) {
return false;
}
return sysUserDeptRoleList.stream()
.anyMatch(x -> {
if (x.getDeptId() != null && x.getSysRole() != null && x.getDeptId().equals(deptId)) {

View File

@ -3,6 +3,7 @@ package com.microservices.common.security.feign;
import feign.RequestInterceptor;
import feign.codec.Encoder;
import feign.form.FormEncoder;
import feign.form.spring.SpringFormEncoder;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
@ -33,6 +34,6 @@ public class FeignAutoConfiguration
// 重要的是返回类型要是 Encoder 并且实现类必须是 FormEncoder 或者其子类
@Bean
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

@ -11,7 +11,6 @@ import com.microservices.common.core.utils.uuid.IdUtils;
import com.microservices.common.redis.service.RedisService;
import com.microservices.common.security.interceptor.HeaderInterceptor;
import com.microservices.common.security.utils.SecurityUtils;
import com.microservices.system.api.RemoteDeptService;
import com.microservices.system.api.RemoteUserService;
import com.microservices.system.api.domain.SysRole;
import com.microservices.system.api.domain.SysUser;
@ -39,8 +38,6 @@ public class TokenService {
private RedisService redisService;
@Autowired
private RemoteUserService remoteUserService;
@Autowired
private RemoteDeptService remoteDeptService;
protected static final long MILLIS_SECOND = 1000;
@ -332,16 +329,4 @@ public class TokenService {
}
return loginUser.getSysUser();
}
public Boolean hasDeptPerms(Long deptId, String perms) {
LoginUser loginUser = SecurityUtils.getLoginUser();
Boolean hasPerms = FeignUtils.getReturnData(
remoteDeptService.checkUserInDeptOrParentDeptHasPerms(deptId, loginUser.getUserId(), perms, SecurityConstants.INNER)
);
if (hasPerms == null) {
hasPerms = false;
}
return hasPerms;
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,6 @@ package com.microservices.cms.doc.controller;
import com.alibaba.fastjson2.JSONObject;
import com.microservices.cms.doc.domain.*;
import com.microservices.cms.doc.service.ICmsDocService;
import com.microservices.common.core.constant.UserConstants;
import com.microservices.common.core.domain.R;
import com.microservices.common.core.web.controller.BaseController;
import com.microservices.common.core.web.domain.AjaxResult;
@ -18,7 +17,6 @@ import io.swagger.annotations.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
/**
@ -108,13 +106,23 @@ public class CmsDocController extends BaseController {
@ApiImplicitParam(name = "pageNum", value = "当前记录起始索引", paramType = "query", dataType = "Integer"),
@ApiImplicitParam(name = "pageSize", value = "每页显示记录数", paramType = "query", dataType = "Integer"),
})
public GenericsTableDataInfo<CmsDocAllDto> docListByZone(
public TableDataInfo docListByZone(
@ApiParam(name = "zoneId", value = "专区Id") @PathVariable("zoneId") Long zoneId
, CmsDocSearchVo cmsDocSearchVo) {
Long deptId = cmsDocService.getDeptIdByZoneId(zoneId);
cmsDocSearchVo.setIsOpen(true);
cmsDocSearchVo.setIsOpen(false);
startPage();
return cmsDocService.selectAllDocList(deptId, cmsDocSearchVo);
List<CmsDocAllDto> list = cmsDocService.selectCmsDocListByDept(deptId, cmsDocSearchVo);
list.forEach(x -> {
String docDetailUrl = cmsDocService.getDocDetailUrl(x.getDeptId(), x.getId());
x.setDocDetailUrl(docDetailUrl);
Boolean editable = cmsDocService.isCmsDocEditable(x);
x.setEditable(editable);
});
int count = cmsDocService.selectCmsDocCountByDept(deptId, cmsDocSearchVo);
return getDataTable(list, count);
}
/**
@ -156,7 +164,7 @@ public class CmsDocController extends BaseController {
/**
* 新增文章信息
*/
@RequiresPermissions(UserConstants.CMS_PERMS_DOCS_ADD)
@RequiresPermissions("cms:doc:add")
@Log(title = "文章信息", businessType = BusinessType.INSERT)
@PostMapping("/dir/{dirId}")
@ApiOperation("在栏目下创建文章")
@ -193,7 +201,7 @@ public class CmsDocController extends BaseController {
/**
* 删除文章
*/
@RequiresPermissions(UserConstants.CMS_PERMS_DOCS_DELETE)
@RequiresPermissions("cms:doc:remove")
@Log(title = "文章信息", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
@ApiOperation("删除文章/栏目")
@ -204,13 +212,13 @@ public class CmsDocController extends BaseController {
/**
* 修改文章信息
*/
@RequiresPermissions(UserConstants.CMS_PERMS_DOCS_EDIT)
@RequiresPermissions("cms:doc:edit")
@Log(title = "文章信息", businessType = BusinessType.UPDATE)
@PutMapping("/{id}")
@ApiOperation("更新文章")
public AjaxResult edit(@ApiParam(name = "id", value = "文章Id") @PathVariable Long id,
@ApiParam(name = "cmsDoc", value = "文章实体对象") @RequestBody CmsDocDto cmsDocDto) {
return success(cmsDocService.updateCmsDoc(id, cmsDocDto));
return toAjax(cmsDocService.updateCmsDoc(id, cmsDocDto));
}
/**
@ -229,12 +237,12 @@ public class CmsDocController extends BaseController {
/**
* 审核文章信息
*/
@RequiresPermissions(UserConstants.CMS_PERMS_DOCS_AUDIT)
@RequiresPermissions("cms:doc:audit")
@Log(title = "文章信息", businessType = BusinessType.UPDATE)
@PutMapping("/{id}/audit")
@ApiOperation("审核文章")
public AjaxResult audit(@ApiParam(name = "id", value = "文章Id") @PathVariable Long id,
@ApiParam(name = "cmsDocAuditVo", value = "文章审核对象") @RequestBody CmsDocAuditVo cmsDocAuditVo) {
@ApiParam(name = "cmsDocAuditVo", value = "文章审核对象") @RequestBody CmsDocAuditVo cmsDocAuditVo) {
return toAjax(cmsDocService.auditCmsDoc(id, cmsDocAuditVo));
}
@ -307,24 +315,6 @@ public class CmsDocController extends BaseController {
return R.ok(cmsDocJSONObject);
}
/**
* 内部接口首页文章合并列表接收置顶文章 docId 有序列表返回合并去重后的结果
* Zone 服务 Feign 调用用于 /open/getTopDoc 改造
*/
@InnerAuth
@PostMapping("/homePageDocMergeList/{zoneId}")
@ApiOperation(value = "首页文章合并列表", hidden = true)
public R<List<JSONObject>> selectHomePageDocMergeList(
@PathVariable("zoneId") Long zoneId,
@RequestBody List<Long> topDocIds) {
List<CmsDocBaseDto> list = cmsDocService.selectHomePageDocMergeList(zoneId, topDocIds);
List<JSONObject> result = new ArrayList<>();
for (CmsDocBaseDto dto : list) {
result.add(JSONObject.from(dto));
}
return R.ok(result);
}
/**
* 通过组织Id获取专区下文章阅读数量
*

View File

@ -5,17 +5,12 @@ import com.microservices.cms.doc.domain.*;
import com.microservices.cms.doc.service.ICmsDocCommentService;
import com.microservices.cms.doc.service.ICmsDocService;
import com.microservices.cms.doc.service.ICmsWechatService;
import com.microservices.cms.search.dto.DocSearchResult;
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.common.core.web.page.TableDataInfo;
import com.microservices.common.log.annotation.Log;
import com.microservices.common.log.enums.BusinessType;
import com.microservices.cms.search.dto.DocSearchRequest;
import com.microservices.cms.search.dto.DocSearchResponse;
import com.microservices.cms.search.service.IGlobalSearchService;
import io.swagger.annotations.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@ -40,22 +35,6 @@ public class OpenController extends BaseController {
@Autowired
private ICmsWechatService cmsWechatService;
@Autowired
private IGlobalSearchService globalSearchService;
/**
* 文章搜索接口
*
* @return 搜索结果
*/
@ApiOperation("文章搜索")
@PostMapping("/search")
public GenericsTableDataInfo<DocSearchResult> docSearch(@RequestBody DocSearchRequest docSearchRequest) {
logger.info("文章搜索请求: keyword={}", docSearchRequest.getKeyword());
DocSearchResponse response = globalSearchService.docSearch(docSearchRequest);
return new GenericsTableDataInfo<>(response.getResults(), response.getTotal());
}
/**
* 查询栏目列表
*/
@ -121,19 +100,6 @@ public class OpenController extends BaseController {
return getAllGenericsDataTableToPage(list);
}
/**
* 查询文章列表不区分栏目
*/
@GetMapping("/zone/{zoneId}/allDocList")
@ApiOperation("查询文章列表(不区分栏目)")
public GenericsTableDataInfo<CmsDocAllDto> allDocList(@ApiParam(name = "zoneId", value = "专区Id") @PathVariable("zoneId") Long zoneId,
CmsDocSearchVo cmsDocSearchVo) {
Long deptId = cmsDocService.getDeptIdByZoneId(zoneId);
cmsDocSearchVo.setIsOpen(true);
startPage();
return cmsDocService.selectAllDocList(deptId, cmsDocSearchVo);
}
/**
* 查询首页文章列表
*/
@ -209,9 +175,9 @@ public class OpenController extends BaseController {
@Log(title = "同步文章数据", businessType = BusinessType.UPDATE)
public AjaxResult syncDocData(
@ApiParam(name = "deptId", value = "组织Id", required = true) @PathVariable("deptId") Long deptId
, @RequestBody() String bodyStr
,@RequestBody() String bodyStr
, HttpServletRequest request) {
cmsDocService.syncDocData(deptId, bodyStr);
cmsDocService.syncDocData(deptId,bodyStr);
return AjaxResult.success("同步消息发送成功");
}

View File

@ -3,7 +3,6 @@ package com.microservices.cms.doc.domain;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.microservices.cms.utils.CmsConstants;
import com.microservices.common.core.annotation.Excel;
import com.microservices.common.core.utils.bean.BeanUtils;
import com.microservices.common.core.web.domain.BaseSortEntity;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ -37,27 +36,19 @@ public class CmsDoc extends BaseSortEntity {
@Excel(name = "文件名称")
private String fileName;
/**
* 文件路径
*/
/** 文件路径 */
@Excel(name = "文件路径")
private String filePath;
/**
* 文件SHA
*/
/** 文件SHA */
@Excel(name = "文件SHA")
private String fileSha;
/**
* 文件大小
*/
/** 文件大小 */
@Excel(name = "文件大小")
private Long fileSize;
/**
* 文件URL
*/
/** 文件URL */
@Excel(name = "文件URL")
private String fileUrl;
@ -171,11 +162,6 @@ public class CmsDoc extends BaseSortEntity {
*/
private String keywords;
/**
* 备注
*/
private String remark;
public CmsDir copyToCmsDir() {
CmsDir cmsDir = new CmsDir();
cmsDir.setId(id);
@ -203,6 +189,7 @@ public class CmsDoc extends BaseSortEntity {
hotCmsDocDto.setName(name);
hotCmsDocDto.setSummary(summary);
hotCmsDocDto.setPublishTime(publishTime);
hotCmsDocDto.setHeadImg(headImg);
return hotCmsDocDto;
}
@ -224,12 +211,30 @@ public class CmsDoc extends BaseSortEntity {
}
private CmsDocBaseDto getCmsDocBaseDto(CmsDocBaseDto cmsDocDto) {
BeanUtils.copyProperties(this, cmsDocDto);
cmsDocDto.setId(id);
cmsDocDto.setName(name);
cmsDocDto.setHeadImg(headImg);
cmsDocDto.setIsHomepage(isHomepage);
cmsDocDto.setSummary(summary);
cmsDocDto.setEditorType(editorType);
cmsDocDto.setDeptId(deptId);
if (visits == null) {
cmsDocDto.setVisits(0L);
} else {
cmsDocDto.setVisits(visits);
}
cmsDocDto.setPublishTime(publishTime);
cmsDocDto.setPublishUser(publishUser);
cmsDocDto.setPublishUserUrl(publishUserUrl);
cmsDocDto.setPublishUserImage(publishUserImage);
cmsDocDto.setPublishUserNickname(publishUserNickname);
cmsDocDto.setAuditStatus(auditStatus);
cmsDocDto.setAuditType(auditType);
cmsDocDto.setCreateBy(createBy);
cmsDocDto.setKeywords(keywords);
return cmsDocDto;
}

View File

@ -20,10 +20,4 @@ public class CmsDocAuditVo {
*/
@ApiModelProperty(value = "是否重新编辑: 0不允许重新编辑 1允许重新编辑")
private Integer reedit;
/**
* 备注
*/
@ApiModelProperty(value = "审核备注")
private String remark;
}

View File

@ -101,10 +101,6 @@ public class CmsDocBaseDto {
*/
@ApiModelProperty("关键词")
private String keywords;
/**
* 备注
*/
private String remark;
public void setPublishTime(Date publishTime) {
if (publishTime != null) {

View File

@ -8,6 +8,7 @@ import lombok.Data;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Date;
/**
* @author otto
@ -51,6 +52,10 @@ public class CmsDocInputDto {
@ApiModelProperty("关键词")
private String keywords;
@ApiModelProperty(value = "发布时间,隐藏参数,内部调用使用", hidden = true)
private Date publishDate;
public String getContent() {
String base64decodedString;
try {

View File

@ -83,10 +83,4 @@ public class CmsDocSearchVo {
*/
@ApiModelProperty("关键词")
private String keywords;
/**
* 排除文章Id
*/
@ApiModelProperty(value = "排除文章Id", hidden = true)
private Long excludeId;
}

View File

@ -1,23 +0,0 @@
package com.microservices.cms.doc.domain;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
/**
* CmsDoc with full-text search score
* 用于全局搜索结果包含全文搜索相关度评分
*
* @author otto
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class CmsDocWithScore extends CmsDoc implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 全文搜索相关度评分来自 MySQL MATCH...AGAINST
*/
private Double searchScore;
}

View File

@ -33,4 +33,6 @@ public class HotCmsDocDto {
@JsonFormat(pattern = "yyyy-MM-dd")
@ApiModelProperty("发布时间")
private Date publishTime;
@ApiModelProperty(value = "文章头图")
private String headImg;
}

View File

@ -2,7 +2,6 @@ package com.microservices.cms.doc.mapper;
import com.microservices.cms.doc.domain.CmsDoc;
import com.microservices.cms.doc.domain.CmsDocSearchVo;
import com.microservices.cms.doc.domain.CmsDocWithScore;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@ -24,14 +23,6 @@ public interface CmsDocMapper {
*/
public CmsDoc selectCmsDocById(Long id);
/**
* 批量查询文章信息
*
* @param ids 文章ID列表
* @return 文章信息列表
*/
List<CmsDoc> selectCmsDocByIds(@Param("ids") List<Long> ids);
/**
* 查询文章信息列表
*
@ -91,11 +82,6 @@ public interface CmsDocMapper {
CmsDoc selectCmsDocByFilePathAndDeptId(@Param("filePath") String filePath, @Param("deptId") Long deptId);
/**
* 按文章名+deptId查询忽略dirName用于Webhook防重判断
*/
CmsDoc selectCmsDocByNameAndDeptIdIgnoreDirName(@Param("name") String name, @Param("deptId") Long deptId);
List<CmsDoc> selectHotCmsDocList(CmsDoc cmsDocInput);
List<CmsDoc> selectCmsDocDirByDeptIdAndInName(@Param("deptId") Long deptId, @Param("cmsDirNameList") List<String> cmsDirNameList);
@ -206,41 +192,4 @@ public interface CmsDocMapper {
List<CmsDoc> selectCmsDocAndDirListByDept(Long deptId);
void updateCmsDocShaByPathAndDeptId(@Param("sha") String sha, @Param("filePath") String filePath, @Param("deptId") Long deptId);
/**
* 全局搜索 - 全文索引查询
*
* @param keyword 搜索关键词
* @param deptId 组织ID可选
* @param offset 偏移量
* @param pageSize 每页数量
* @return 搜索结果列表
*/
List<CmsDoc> globalSearch(@Param("keyword") String keyword, @Param("deptId") Long deptId,
@Param("offset") int offset, @Param("pageSize") int pageSize);
/**
* 全局搜索结果计数
*
* @param keyword 搜索关键词
* @param deptId 组织ID可选
* @return 结果数量
*/
Long globalSearchCount(@Param("keyword") String keyword, @Param("deptId") Long deptId);
/**
* 全局搜索 - 全文索引查询含评分
*
* @param keyword 搜索关键词
* @param deptId 组织ID可选
* @param offset 偏移量
* @param pageSize 每页数量
* @return 搜索结果列表含评分
*/
List<CmsDocWithScore> globalSearchWithScore(
@Param("keyword") String keyword,
@Param("deptId") Long deptId,
@Param("offset") int offset,
@Param("pageSize") int pageSize
);
}

View File

@ -99,38 +99,4 @@ public interface ICmsAsyncService {
* @param cmsDoc 文章
*/
void pushDocDataSearchEngine(CmsDoc cmsDoc);
void clearTmpDoc(CmsProject cmsProject,List<CmsDoc> tmpDocList);
/**
* 异步增加文章发布积分
*
* @param deptId 组织ID
* @param createBy 创建人用户名
* @param docId 文章ID
* @param subjectDesc 文章标题
*/
void addPublishDocPointsAsync(Long deptId, String createBy, Long docId, String subjectDesc);
/**
* 异步增加文章浏览积分每满100次+2上限50
*
* @param deptId 组织ID
* @param createBy 创建人用户名
* @param docId 文章ID
* @param docName 文章名称
* @param totalVisits 累计阅读次数
* @param pointsTriggerCount 当前第几次增加积分
*/
void addVisitsDocPointsAsync(Long deptId, String createBy, Long docId, String docName, Long totalVisits);
/**
* 异步增加文章被设为精选的积分
*
* @param deptId 组织ID
* @param createBy 创建人用户名
* @param docId 文章ID
* @param subjectDesc 文章标题
*/
void addFeaturedDocPointsAsync(Long deptId, String createBy, Long docId, String subjectDesc);
}

View File

@ -5,7 +5,6 @@ import com.microservices.cms.doc.domain.*;
import com.microservices.cms.doc.domain.gitlink.EntryDto;
import com.microservices.cms.doc.domain.gitlink.FileEntryDto;
import com.microservices.cms.project.domain.CmsProject;
import com.microservices.common.core.web.page.GenericsTableDataInfo;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
@ -57,9 +56,9 @@ public interface ICmsDocService {
*
* @param id
* @param cmsDocDto 文章信息
* @return 结果 文章id
* @return 结果
*/
public Long updateCmsDoc(Long id, CmsDocDto cmsDocDto);
public boolean updateCmsDoc(Long id, CmsDocDto cmsDocDto);
/**
* 批量删除文章信息
@ -145,15 +144,6 @@ public interface ICmsDocService {
*/
List<CmsDocBaseDto> selectHomePageDocList(Long deptId);
/**
* 查询首页文章合并列表积分置顶文章 + 首页推荐文章自动去重置顶在前
*
* @param zoneId 专区ID
* @param topDocIds 置顶文章 docId 有序列表前端按此顺序排列
* @return 合并后的首页文章列表isTop=true 标识置顶文章
*/
List<CmsDocBaseDto> selectHomePageDocMergeList(Long zoneId, List<Long> topDocIds);
/**
* 初始化栏目
*
@ -208,8 +198,6 @@ public interface ICmsDocService {
*/
List<CmsDocAllDto> selectCmsDocListByDept(Long deptId, CmsDocSearchVo cmsDocSearchVo);
GenericsTableDataInfo<CmsDocAllDto> selectAllDocList(Long deptId, CmsDocSearchVo cmsDocSearchVo);
/**
* 查询专区下文章数量
*
@ -306,6 +294,4 @@ public interface ICmsDocService {
List<CmsDoc> selectCmsDocAndDirListByDept(Long deptId);
void updateCmsDocShaByPathAndDeptId(String sha, String filePath, Long deptId);
List<CmsDoc> getTmpDocList(CmsDoc cmsDoc);
}

View File

@ -20,8 +20,6 @@ import com.microservices.cms.utils.CustomExecutorFactory;
import com.microservices.common.core.constant.CacheConstants;
import com.microservices.common.core.constant.Constants;
import com.microservices.common.core.constant.SecurityConstants;
import com.microservices.common.core.enums.DocAndZoneOperation;
import com.microservices.common.core.enums.DocAuditStatus;
import com.microservices.common.core.enums.SystemRole;
import com.microservices.common.core.exception.ServiceException;
import com.microservices.common.core.utils.DateUtils;
@ -35,7 +33,6 @@ import com.microservices.common.httpClient.util.GitLinkRequestHelper;
import com.microservices.common.redis.service.RedisService;
import com.microservices.common.security.utils.SecurityUtils;
import com.microservices.system.api.RemoteDeptService;
import com.microservices.system.api.RemoteUserService;
import com.microservices.system.api.RemoteZoneService;
import com.microservices.system.api.utils.FeignUtils;
import org.apache.http.client.config.RequestConfig;
@ -98,8 +95,6 @@ public class CmsAsyncServiceImpl implements ICmsAsyncService {
@Autowired
private RemoteZoneService remoteZoneService;
@Autowired
private RemoteUserService remoteUserService;
@Autowired
private RequestConfig config;
/**
@ -256,16 +251,7 @@ public class CmsAsyncServiceImpl implements ICmsAsyncService {
}
CmsDoc cmsDoc = cmsDocMapper.selectCmsDocByNameAndDeptId(CmsUtils.getFileNameNoSuffix(entry.getName()), cmsProject.getDeptId(), dirName);
if (cmsDoc == null) {
// 防重按文章名+deptId查一次排除正在移动栏目的已有记录
String docName = CmsUtils.getFileNameNoSuffix(entry.getName());
CmsDoc existingDoc = cmsDocMapper.selectCmsDocByNameAndDeptIdIgnoreDirName(docName, cmsProject.getDeptId());
if (existingDoc != null) {
existingDoc.setDirName(dirName);
existingDoc.setFilePath(dirName + "/" + existingDoc.getFileName());
cmsDocMapper.updateCmsDoc(existingDoc);
} else {
syncCreateDoc(entry, dirName, cmsProject.getDeptId(), fileEntry.getLast_commit());
}
syncCreateDoc(entry, dirName, cmsProject.getDeptId(), fileEntry.getLast_commit());
}
}
} catch (Exception e) {
@ -632,12 +618,11 @@ public class CmsAsyncServiceImpl implements ICmsAsyncService {
100, "..."), tmpBranchName);
} catch (ServiceException e) {
//忽略重复创建PR异常
if (!e.getMessage().contains("合并请求已存在")) {
if (!"请求GitLink失败(合并请求已存在)".equals(e.getMessage())) {
throw new ServiceException(e.getMessage());
}
}
cmsDocService.updateCmsCommonDatabase(cmsDoc, tmpBranchName);
}
@ -738,8 +723,8 @@ public class CmsAsyncServiceImpl implements ICmsAsyncService {
if (isChangeName || isChangeDir) {
oldCmsDoc = renameAndChangeDirCmsDoc(oldCmsDoc, cmsProject, newName, newDirName, branchName);
// 立即更新数据库中的dirName/filePath避免GitLink Webhook回调时查不到记录导致重复INSERT
cmsDocMapper.updateCmsDoc(oldCmsDoc);
//todo 此处是否需要更新数据库待讨论
//updateCmsCommonDatabase(oldCmsDoc);
}
boolean isChangeContent = false;
@ -1018,23 +1003,6 @@ public class CmsAsyncServiceImpl implements ICmsAsyncService {
}
}
@Override
public void clearTmpDoc(CmsProject cmsProject, List<CmsDoc> tmpDocList) {
CustomExecutorFactory.threadPoolExecutor.execute(() -> {
for (CmsDoc tmpDoc : tmpDocList) {
//拒绝GitLink合并请求
String gitLinkPullRequestNumber = tmpDoc.getAuditPrNumber().split("-")[0];
gitLinkRequestHelper.doPost(
CmsGitLinkRequestUrl.REFUSE_MERGE_PULL_REQUEST(cmsProject, gitLinkPullRequestNumber), null);
gitLinkRequestHelper.doPost(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, tmpDoc.getAuditTmpBranchName()), null);
// 所有删除操作调整为将审核状态设置为已拒绝
tmpDoc.setAuditStatus(DocAuditStatus.AUDIT_FAIL.getAuditCode());
tmpDoc.setRemark("管理员删除");
cmsDocMapper.updateCmsDoc(tmpDoc);
}
});
}
private void moveCmsDocToNewCmsDir(CmsProject cmsProject, String oldDirName, String newDirName, String message) {
CmsDocSearchVo cmsDocSearch = new CmsDocSearchVo();
cmsDocSearch.setIsDir(false);
@ -1087,127 +1055,4 @@ public class CmsAsyncServiceImpl implements ICmsAsyncService {
}
}
/**
* 检查是否为积分部门
*
* @param deptId 部门ID
* @return 是否为积分部门
*/
private boolean isPointsDept(Long deptId) {
Long cachedPointsDeptId = redisService.getCacheObject(CacheConstants.ZONE_POINTS_DEPT_ID_KEY);
return cachedPointsDeptId != null && cachedPointsDeptId > 0 && cachedPointsDeptId.equals(deptId);
}
@Async
@Override
public void addPublishDocPointsAsync(Long deptId, String createBy, Long docId, String subjectDesc) {
try {
if (deptId == null || createBy == null) {
logger.warn("文章审核通过增加积分失败deptId或createBy为空docId={}", docId);
return;
}
// 检查是否为积分部门
if (!isPointsDept(deptId)) {
logger.info("文章审核通过增加积分跳过非积分部门deptId={}docId={}", deptId, docId);
return;
}
// 获取用户ID
com.microservices.system.api.domain.SysUser sysUser = FeignUtils.getReturnData(
remoteUserService.getSysUserByUserName(createBy, SecurityConstants.INNER));
if (sysUser == null || sysUser.getUserId() == null) {
logger.warn("文章审核通过增加积分失败未找到用户信息createBy={}docId={}", createBy, docId);
return;
}
// 直接写入 Redis 积分队列 Zone 模块定时任务消费
JSONObject task = new JSONObject();
task.put("sourceType", CmsConstants.PUBLISH_DOC);
task.put("userId", sysUser.getUserId());
task.put("deptId", deptId);
task.put("sourceId", docId);
task.put("subjectDesc", subjectDesc);
task.put("operatorId", sysUser.getUserId());
redisService.leftPush(CmsConstants.ZONE_POINTS_ASYNC_QUEUE_KEY, task.toJSONString());
logger.info("文章审核通过增加积分任务已写入队列docId={}userId={}deptId={}", docId, sysUser.getUserId(), deptId);
} catch (Exception e) {
// 捕获异常确保不影响主事务
logger.error("文章审核通过增加积分异常docId={}", docId, e);
}
}
@Async
@Override
public void addVisitsDocPointsAsync(Long deptId, String createBy, Long docId, String docName, Long totalVisits) {
try {
if (deptId == null || createBy == null) {
logger.warn("文章浏览增加积分失败参数不合法deptId={}createBy={}", deptId, createBy);
return;
}
// 检查是否为积分部门
if (!isPointsDept(deptId)) {
logger.info("文章浏览增加积分跳过非积分部门deptId={}docId={}", deptId, docId);
return;
}
// 获取用户ID
com.microservices.system.api.domain.SysUser sysUser = FeignUtils.getReturnData(
remoteUserService.getSysUserByUserName(createBy, SecurityConstants.INNER));
if (sysUser == null || sysUser.getUserId() == null) {
logger.warn("文章浏览增加积分失败未找到用户信息createBy={}docId={}", createBy, docId);
return;
}
// 直接写入 Redis 积分队列 Zone 模块定时任务消费
JSONObject task = new JSONObject();
task.put("sourceType", CmsConstants.VISITS_DOC);
task.put("userId", sysUser.getUserId());
task.put("deptId", deptId);
task.put("sourceId", docId);
// JSONObject sourceData = new JSONObject();
// sourceData.put("totalVisits", totalVisits);
// sourceData.put("currentIncrement", pointsTriggerCount);
task.put("sourceData", totalVisits);
task.put("subjectDesc", docName);
task.put("operatorId", SecurityUtils.getUserId());
redisService.leftPush(CmsConstants.ZONE_POINTS_ASYNC_QUEUE_KEY, task.toJSONString());
logger.info("文章浏览增加积分任务已写入队列docId={}userId={}deptId={}totalVisits={}", docId, sysUser.getUserId(), deptId, totalVisits);
} catch (Exception e) {
// 捕获异常确保不影响主事务
logger.error("文章浏览增加积分异常docId={}", docId, e);
}
}
@Async
@Override
public void addFeaturedDocPointsAsync(Long deptId, String createBy, Long docId, String subjectDesc) {
try {
if (deptId == null || createBy == null) {
logger.warn("积分-文章精选参数不合法deptId={}createBy={}", deptId, createBy);
return;
}
// 检查是否为积分部门
if (!isPointsDept(deptId)) {
logger.info("积分-文章精选非积分部门deptId={}docId={}", deptId, docId);
return;
}
// 获取用户ID
com.microservices.system.api.domain.SysUser sysUser = FeignUtils.getReturnData(
remoteUserService.getSysUserByUserName(createBy, SecurityConstants.INNER));
if (sysUser == null || sysUser.getUserId() == null) {
logger.warn("积分-文章精选未找到用户信息createBy={}docId={}", createBy, docId);
return;
}
// 直接写入 Redis 积分队列 Zone 模块定时任务消费
JSONObject task = new JSONObject();
task.put("sourceType", CmsConstants.FEATURED_DOC);
task.put("userId", sysUser.getUserId());
task.put("deptId", deptId);
task.put("sourceId", docId);
task.put("subjectDesc", "" + subjectDesc + "》管理员标记");
task.put("operatorId", SecurityUtils.getUserId());
redisService.leftPush(CmsConstants.ZONE_POINTS_ASYNC_QUEUE_KEY, task.toJSONString());
logger.info("文章被设为精选增加积分任务已写入队列docId={}userId={}deptId={}", docId, sysUser.getUserId(), deptId);
} catch (Exception e) {
// 捕获异常确保不影响主事务
logger.error("文章被设为精选增加积分异常docId={}", docId, e);
}
}
}

View File

@ -15,7 +15,6 @@ import com.microservices.cms.utils.CmsGitLinkRequestUrl;
import com.microservices.common.core.constant.CacheConstants;
import com.microservices.common.core.constant.HttpStatus;
import com.microservices.common.core.constant.SecurityConstants;
import com.microservices.common.core.constant.UserConstants;
import com.microservices.common.core.enums.DocAndZoneOperation;
import com.microservices.common.core.enums.DocAuditStatus;
import com.microservices.common.core.enums.SystemRole;
@ -24,12 +23,10 @@ import com.microservices.common.core.utils.DateUtils;
import com.microservices.common.core.utils.StringUtils;
import com.microservices.common.core.utils.bean.BeanUtils;
import com.microservices.common.core.utils.html.EscapeUtil;
import com.microservices.common.core.web.page.GenericsTableDataInfo;
import com.microservices.common.httpClient.constant.GitLinkConstants;
import com.microservices.common.httpClient.util.GitLinkRequestHelper;
import com.microservices.common.redis.service.RedisService;
import com.microservices.common.security.auth.AuthUtil;
import com.microservices.common.security.service.TokenService;
import com.microservices.common.security.utils.SecurityUtils;
import com.microservices.system.api.RemoteDeptService;
import com.microservices.system.api.RemoteUserService;
@ -50,11 +47,8 @@ import javax.servlet.http.HttpServletRequest;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
@ -86,8 +80,6 @@ public class CmsDocServiceImpl implements ICmsDocService {
@Autowired
private RemoteDeptService remoteDeptService;
@Autowired
private TokenService tokenService;
@Autowired
@Lazy
private ICmsDocCommentService cmsDocCommentService;
@Value("${cms.detailPath:/newdetail}")
@ -97,7 +89,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
* 查询文章信息
*
* @param id 文章信息主键
* @param isOpen 是否公开访问
* @param isOpen
* @return 文章信息
*/
@Override
@ -112,14 +104,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
CmsProject cmsProject = cmsProjectService.selectCmsProjectByDeptId(cmsDoc.getDeptId());
// 确定查询文件的分支已发布文章或非仓库改动使用 master其他使用临时分支
String branchName;
if (DocAuditStatus.AUDIT_PASS.getAuditCode().equals(cmsDoc.getAuditStatus())
|| (cmsDoc.getAuditType() != null && cmsDoc.getAuditType().contains(DocAndZoneOperation.OPERATION_NON_REPO_UPDATE.getOperationCode()))) {
branchName = CmsConstants.BRANCH_MASTER;
} else {
branchName = cmsDoc.getAuditTmpBranchName();
}
String branchName = DocAuditStatus.AUDIT_PASS.getAuditCode().equals(cmsDoc.getAuditStatus()) ? CmsConstants.BRANCH_MASTER : cmsDoc.getAuditTmpBranchName();
FileEntryDto fileEntryDto = getFileEntryDto(cmsProject, cmsDoc.getFilePath(), branchName);
EntryDto entryDto = fileEntryDto.getEntries();
@ -130,7 +115,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
if (cmsDoc.getPublishTime() != null && cmsDoc.getPublishTime().after(cmsDoc.getCreateTime())) {
cmsDocDto.setIsFirstPublish(false);
}
if (isOpen && DocAuditStatus.AUDIT_PASS.getAuditCode().equals(cmsDoc.getAuditStatus())) {
if (isOpen) {
updateVisits(cmsDocDto.getId());
}
return cmsDocDto;
@ -186,21 +171,6 @@ public class CmsDocServiceImpl implements ICmsDocService {
return cmsDocList.stream().map(CmsDoc::copyToCmsDocAllDto).collect(Collectors.toList());
}
@Override
public GenericsTableDataInfo<CmsDocAllDto> selectAllDocList(Long deptId, CmsDocSearchVo cmsDocSearchVo) {
List<CmsDocAllDto> list = selectCmsDocListByDept(deptId, cmsDocSearchVo);
list.forEach(x -> {
String docDetailUrl = getDocDetailUrl(x.getDeptId(), x.getId());
x.setDocDetailUrl(docDetailUrl);
Boolean editable = isCmsDocEditable(x);
x.setEditable(editable);
});
int count = selectCmsDocCountByDept(deptId, cmsDocSearchVo);
return new GenericsTableDataInfo<>(list, Long.valueOf(count));
}
@Override
public int selectCmsDocCountByDept(Long deptId, CmsDocSearchVo cmsDocSearchVo) {
cmsDocSearchVo.setDeptId(deptId);
@ -263,14 +233,14 @@ public class CmsDocServiceImpl implements ICmsDocService {
@Override
public List<CmsDocDetailDto> selectCmsDocDetailList(Long dirId) {
List<CmsDocDetailDto> cmsDocDetailDtoList = new ArrayList<>();
CmsDoc cmsDir = selectCmsDir(dirId);
CmsDoc cmsDir = cmsDocMapper.selectCmsDocDirById(dirId);
CmsProject cmsProject = cmsProjectService.selectCmsProjectByDeptId(cmsDir.getDeptId());
CmsDocSearchVo cmsDocSearchVo = new CmsDocSearchVo();
cmsDocSearchVo.setDirName(cmsDir.getName());
cmsDocSearchVo.setDeptId(cmsDir.getDeptId());
cmsDocSearchVo.setIsDir(false);
List<CmsDoc> cmsDocList = cmsDocMapper.selectCmsDocListSetDefaultHeadImg(cmsDocSearchVo);
List<CompletableFuture<CmsDocDetailDto>> futures = new ArrayList<>();
List<CompletableFuture<CmsDocDetailDto>> futures = new ArrayList();
for (CmsDoc cmsDoc : cmsDocList) {
CompletableFuture<CmsDocDetailDto> future = CompletableFuture.supplyAsync(() -> {
CmsDocDetailDto cmsDocDetailDto = cmsDoc.copyToCmsDocDetailDto();
@ -327,7 +297,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
@Override
public void updateCmsDocShaByPathAndDeptId(String sha, String filePath, Long deptId) {
cmsDocMapper.updateCmsDocShaByPathAndDeptId(sha, filePath, deptId);
cmsDocMapper.updateCmsDocShaByPathAndDeptId(sha,filePath,deptId);
}
private List<CmsDocBaseDto> selectCmsDocListByDatabase(Long dirId, CmsDocSearchVo cmsDocSearchVo) {
@ -344,8 +314,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
/**
* 设置文章默认头图为栏目头图
*
* @param dirId 栏目id
* @param dirId 栏目id
* @param cmsDocSearchVo 文章查询条件
* @return 文章列表
*/
@ -390,16 +359,13 @@ public class CmsDocServiceImpl implements ICmsDocService {
checkCmsLockStatus(dirDoc.getDeptId());
CmsDoc cmsDoc = cmsDocInputDto.toCmsDoc(dirDoc);
if (isNoAuditRequired(dirDoc.getDeptId()) || tokenService.hasDeptPerms(dirDoc.getDeptId(), UserConstants.CMS_PERMS_DOCS_AUDIT)) {
if (isNoAuditRequired(dirDoc.getDeptId()) || isCurrentUserZoneAdmin(dirDoc.getDeptId())) {
//文章自动置为审核通过状态值
cmsDoc.setAuditStatus(DocAuditStatus.AUDIT_PASS.getAuditCode());
boolean success = insertCommonCmsDoc(cmsDoc,
"创建文章:" + StringUtils.truncateStringAndConcatenatingSpecifiedString(cmsDoc.getName(), 100, "..."),
cmsDocInputDto.getContent());
if (success) {
// 自动审核通过时也需要增加积分
cmsAsyncService.addPublishDocPointsAsync(cmsDoc.getDeptId(), cmsDoc.getCreateBy(), cmsDoc.getId(), cmsDoc.getName());
}
innerInitPublishDate(cmsDoc,cmsDocInputDto.getPublishDate());
return success ? cmsDoc.getId().intValue() : null;
} else {
//创建临时分支创建文件创建PR
@ -409,11 +375,22 @@ public class CmsDocServiceImpl implements ICmsDocService {
boolean success = insertCmsDocWithAudit(cmsDoc,
"创建文章:" + StringUtils.truncateStringAndConcatenatingSpecifiedString(cmsDoc.getName(), 100, "..."),
cmsDocInputDto.getContent());
innerInitPublishDate(cmsDoc,cmsDocInputDto.getPublishDate());
return success ? cmsDoc.getId().intValue() : null;
}
}
private void innerInitPublishDate(CmsDoc cmsDoc, Date publishDate) {
if(publishDate!=null){
cmsDoc.setCreateTime(publishDate);
cmsDoc.setUpdateTime(publishDate);
cmsDoc.setPublishTime(publishDate);
cmsDocMapper.updateCmsDoc(cmsDoc);
}
}
private boolean isCurrentUserZoneAdmin(Long deptId) {
boolean isZoneAdmin = false;
List<SysUserDeptRole> sysUserDeptRoleList = FeignUtils.getReturnData(
@ -437,9 +414,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
public String getCmsDocFileContent(CmsProject cmsProject, CmsDoc cmsDoc) {
String branchName = CmsConstants.BRANCH_MASTER;
if (!DocAuditStatus.AUDIT_PASS.getAuditCode().equals(cmsDoc.getAuditStatus())) {
if (StringUtils.isNotEmpty(cmsDoc.getAuditTmpBranchName())) {
branchName = cmsDoc.getAuditTmpBranchName();
}
branchName = cmsDoc.getAuditTmpBranchName();
}
//当删除文章为文件夹时判断该文件夹下是否存在文章若存在需先删除所有文章
JSONObject getSubEntriesResult = gitLinkRequestHelper.doGet(
@ -459,34 +434,9 @@ public class CmsDocServiceImpl implements ICmsDocService {
@Transactional(rollbackFor = Exception.class)
public boolean auditCmsDoc(Long docId, CmsDocAuditVo cmsDocAuditVo) {
CmsDoc cmsDoc = cmsDocMapper.selectCmsDocById(docId);
if (cmsDoc == null) {
throw new ServiceException("该文章不存在");
}
checkCmsLockStatus(cmsDoc.getDeptId());
CmsProject cmsProject = cmsProjectService.selectCmsProjectByDeptId(cmsDoc.getDeptId());
// 设置审核备注
cmsDoc.setRemark(cmsDocAuditVo.getRemark());
if (cmsDocAuditVo.getPass() == 1) {
// 处理非仓库改动的审核通过
if (cmsDoc.getAuditType() != null
&& cmsDoc.getAuditType().contains(DocAndZoneOperation.OPERATION_NON_REPO_UPDATE.getOperationCode())) {
Long oldCmsDocId = Long.parseLong(cmsDoc.getAuditType().split("-")[1]);
CmsDoc oldCmsDoc = cmsDocMapper.selectCmsDocById(oldCmsDocId);
// 用新文章的非仓库字段更新旧文章
oldCmsDoc.setHeadImg(cmsDoc.getHeadImg());
oldCmsDoc.setSummary(cmsDoc.getSummary());
oldCmsDoc.setKeywords(cmsDoc.getKeywords());
oldCmsDoc.setRemark(cmsDoc.getRemark());
cmsDocMapper.updateCmsDoc(oldCmsDoc);
// 删除待审核记录
cmsDocMapper.deleteCmsDocById(cmsDoc.getId());
return true;
}
//同意合并请求
JSONObject result = mergePullRequestByGitLink(cmsProject, cmsDoc);
if (result.containsKey(GitLinkConstants.STATUS_KEY)) {
@ -508,16 +458,8 @@ public class CmsDocServiceImpl implements ICmsDocService {
}
}
if (cmsDocAuditVo.getPass() == 0) {
// 处理非仓库改动的审核拒绝
if (cmsDoc.getAuditType().contains(DocAndZoneOperation.OPERATION_NON_REPO_UPDATE.getOperationCode())) {
handleCmsDocWithNoPassByReedit(cmsDocAuditVo, cmsDoc, cmsProject);
return true;
}
//拒绝GitLink合并请求
String gitLinkPullRequestNumber = cmsDoc.getAuditPrNumber().split("-")[0];
JSONObject result = gitLinkRequestHelper.doPost(
CmsGitLinkRequestUrl.REFUSE_MERGE_PULL_REQUEST(cmsProject, gitLinkPullRequestNumber), null);
JSONObject result = refuseMergePullRequestByGitLink(cmsDoc, cmsProject);
if (result.containsKey(GitLinkConstants.STATUS_KEY)) {
//2.若拒绝GitLink合并请求操作返回正确的状态值,更新数据库
if (result.getInteger(GitLinkConstants.STATUS_KEY).equals(1)) {
@ -564,10 +506,8 @@ public class CmsDocServiceImpl implements ICmsDocService {
if (cmsDocAuditVo.getReedit().equals(0)) {
//删除待审核文章
cmsDocMapper.deleteCmsDocById(cmsDoc.getId());
// 删除分支如果有
if (StringUtils.isNotEmpty(cmsDoc.getAuditTmpBranchName())) {
gitLinkRequestHelper.doPost(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()), null);
}
//删除分支
gitLinkRequestHelper.doDelete(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()));
return;
}
}
@ -579,7 +519,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
int success = deleteCmsDocDatabaseById(ids);
if (success == ids.length) {
//删除临时分支
gitLinkRequestHelper.doPost(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()), null);
gitLinkRequestHelper.doDelete(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()));
}
}
@ -599,7 +539,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
int success = cmsDocMapper.deleteCmsDocById(newDocId);
if (success > 0) {
//删除临时分支
gitLinkRequestHelper.doPost(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()), null);
gitLinkRequestHelper.doDelete(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()));
}
}
@ -613,13 +553,11 @@ public class CmsDocServiceImpl implements ICmsDocService {
int success = cmsDocMapper.updateCmsDoc(cmsDoc);
if (success > 0) {
//删除临时分支
gitLinkRequestHelper.doPost(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()), null);
gitLinkRequestHelper.doDelete(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()));
}
// 推送文章地址到搜索引擎
cmsAsyncService.pushDocDataSearchEngine(cmsDoc);
// 文章审核通过后给作者增加积分异步执行不影响主事务
cmsAsyncService.addPublishDocPointsAsync(cmsDoc.getDeptId(), cmsDoc.getCreateBy(), cmsDoc.getId(), cmsDoc.getName());
}
private JSONObject mergePullRequestByGitLink(CmsProject cmsProject, CmsDoc cmsDoc) {
@ -634,6 +572,12 @@ public class CmsDocServiceImpl implements ICmsDocService {
CmsGitLinkRequestUrl.MERGE_PULL_REQUEST(cmsProject, gitLinkPullRequestId), requestBody);
}
private JSONObject refuseMergePullRequestByGitLink(CmsDoc cmsDoc, CmsProject cmsProject) {
//1.调用gitlink拒绝合并请求接口
return gitLinkRequestHelper.doPost(
CmsGitLinkRequestUrl.REFUSE_MERGE_PULL_REQUEST(cmsProject, cmsDoc.getAuditPrNumber()), null);
}
public static String sha1(String data) {
try {
MessageDigest md = MessageDigest.getInstance("SHA1");
@ -775,47 +719,11 @@ public class CmsDocServiceImpl implements ICmsDocService {
* @return 结果
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Long updateCmsDoc(Long id, CmsDocDto cmsDocDto) {
public boolean updateCmsDoc(Long id, CmsDocDto cmsDocDto) {
CmsDoc oldCmsDoc = cmsDocMapper.selectCmsDocById(id);
checkCmsLockStatus(oldCmsDoc.getDeptId());
if (!hasRepoRelatedChanged(oldCmsDoc, cmsDocDto)) {
// 更新非仓库字段到原文章对象
if (StringUtils.isNotEmpty(cmsDocDto.getHeadImg())) {
oldCmsDoc.setHeadImg(cmsDocDto.getHeadImg());
}
if (StringUtils.isNotEmpty(cmsDocDto.getSummary())) {
oldCmsDoc.setSummary(cmsDocDto.getSummary());
}
if (StringUtils.isNotEmpty(cmsDocDto.getKeywords())) {
oldCmsDoc.setKeywords(cmsDocDto.getKeywords());
}
// 管理员或免审核直接更新原记录
if (tokenService.hasDeptPerms(oldCmsDoc.getDeptId(), UserConstants.CMS_PERMS_DOCS_AUDIT)
|| isNoAuditRequired(oldCmsDoc.getDeptId())) {
cmsDocMapper.updateCmsDoc(oldCmsDoc);
return oldCmsDoc.getId();
}
// 需要审核创建新的待审核记录与仓库改动逻辑一致
if (oldCmsDoc.getAuditStatus().equals(DocAuditStatus.AUDIT_PASS.getAuditCode())) {
CmsDoc newDocWithAudit = insertDocWithAudit(oldCmsDoc,
DocAndZoneOperation.OPERATION_NON_REPO_UPDATE.getOperationCode() + "-" + oldCmsDoc.getId(),
DocAuditStatus.TO_BE_AUDIT.getAuditCode());
return newDocWithAudit.getId();
}
oldCmsDoc.setAuditStatus(DocAuditStatus.TO_BE_AUDIT.getAuditCode());
if (oldCmsDoc.getAuditType().contains(DocAndZoneOperation.OPERATION_CREATE.getOperationCode())
|| oldCmsDoc.getAuditType().contains(DocAndZoneOperation.OPERATION_UPDATE.getOperationCode())) {
CmsProject cmsProject = cmsProjectService.selectCmsProjectByDeptId(oldCmsDoc.getDeptId());
// 重新开启PR
String gitLinkPullRequestNumber = oldCmsDoc.getAuditPrNumber().split("-")[0];
gitLinkRequestHelper.doPost(CmsGitLinkRequestUrl.REOPEN_PULL_REQUEST(cmsProject, gitLinkPullRequestNumber), null);
}
// 待审核/未通过文章重新编辑
cmsDocMapper.updateCmsDoc(oldCmsDoc);
return oldCmsDoc.getId();
return cmsDocMapper.updateCmsDoc(oldCmsDoc) > 0;
}
// 当文章名称发生更改时判断文章名是否在当前栏目下已存在
if (StringUtils.isNotEmpty(cmsDocDto.getName()) && !cmsDocDto.getName().equals(oldCmsDoc.getName())) {
@ -828,16 +736,8 @@ public class CmsDocServiceImpl implements ICmsDocService {
}
checkDocName(cmsDocDto.getName(), oldCmsDoc.getDeptId(), dirName);
}
// 当用户更新栏目时需检查新栏目下是否已存在相同文章名
if (cmsDocDto.getDirId() != null) {
CmsDir cmsDir = selectCmsDirById(cmsDocDto.getDirId(), false);
if (cmsDir != null && !cmsDir.getName().equals(oldCmsDoc.getDirName())) {
checkDocName(cmsDocDto.getName(), oldCmsDoc.getDeptId(), cmsDir.getName());
}
}
//用户为专区管理员或专区无审核机制时直接更新文章及仓库主分支
if (tokenService.hasDeptPerms(oldCmsDoc.getDeptId(), UserConstants.CMS_PERMS_DOCS_AUDIT) || isNoAuditRequired(oldCmsDoc.getDeptId())) {
if (isCurrentUserZoneAdmin(oldCmsDoc.getDeptId()) || isNoAuditRequired(oldCmsDoc.getDeptId())) {
//专区管理员重新编辑并提交审核未通过文章 重新编辑并提交待审核文章时专区不需审核机制
if (!oldCmsDoc.getAuditStatus().equals(DocAuditStatus.AUDIT_PASS.getAuditCode())) {
//文章审核类型update-原文章id 取出原文章id直接更新删除待审核更新类型文章
@ -852,8 +752,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
if (oldCmsDoc.getAuditType() != null && oldCmsDoc.getAuditType().contains(DocAndZoneOperation.OPERATION_CREATE.getOperationCode())) {
deleteTmpBranchIfBranchExist(oldCmsDoc);
cmsDocMapper.deleteCmsDocById(id);
directlyInsertAuditedCmsDocWhenZoneHasNoAuditRequired(cmsDocDto, oldCmsDoc);
return oldCmsDoc.getId();
return directlyInsertAuditedCmsDocWhenZoneHasNoAuditRequired(cmsDocDto, oldCmsDoc);
}
}
String branchName = CmsConstants.BRANCH_MASTER;
@ -861,52 +760,16 @@ public class CmsDocServiceImpl implements ICmsDocService {
oldCmsDoc.setAuditStatus(DocAuditStatus.AUDIT_PASS.getAuditCode());
oldCmsDoc.setAuditTmpBranchName(null);
oldCmsDoc.setKeywords(cmsDocDto.getKeywords());
updateCmsCommonDatabase(oldCmsDoc, CmsConstants.BRANCH_MASTER);
return oldCmsDoc.getId();
return updateCmsCommonDatabase(oldCmsDoc, CmsConstants.BRANCH_MASTER) > 0;
} else {
CmsDoc newDocWithAudit = new CmsDoc();
//待审核未通过文章重新编辑并提交
if (DocAuditStatus.AUDIT_FAIL.getAuditCode().equals(oldCmsDoc.getAuditStatus())
|| DocAuditStatus.TO_BE_AUDIT.getAuditCode().equals(oldCmsDoc.getAuditStatus())) {
// 非仓库改动待审核记录检查是否有仓库改动
if (oldCmsDoc.getAuditType() != null
&& oldCmsDoc.getAuditType().contains(DocAndZoneOperation.OPERATION_NON_REPO_UPDATE.getOperationCode())) {
if (!hasRepoRelatedChanged(oldCmsDoc, cmsDocDto)) {
// 纯非仓库改动继续在当前记录上更新
oldCmsDoc.setAuditStatus(DocAuditStatus.TO_BE_AUDIT.getAuditCode());
if (StringUtils.isNotEmpty(cmsDocDto.getHeadImg())) {
oldCmsDoc.setHeadImg(cmsDocDto.getHeadImg());
}
if (StringUtils.isNotEmpty(cmsDocDto.getSummary())) {
oldCmsDoc.setSummary(cmsDocDto.getSummary());
}
if (StringUtils.isNotEmpty(cmsDocDto.getKeywords())) {
oldCmsDoc.setKeywords(cmsDocDto.getKeywords());
}
cmsDocMapper.updateCmsDoc(oldCmsDoc);
return oldCmsDoc.getId();
} else {
// 有仓库改动转为仓库改动类型删除当前记录重新创建
Long oldCmsDocId = Long.parseLong(oldCmsDoc.getAuditType().split("-")[1]);
CmsDoc originCmsDoc = cmsDocMapper.selectCmsDocById(oldCmsDocId);
cmsDocMapper.deleteCmsDocById(oldCmsDoc.getId());
// 按照仓库改动逻辑重新创建
newDocWithAudit = insertDocWithAudit(originCmsDoc,
DocAndZoneOperation.OPERATION_UPDATE.getOperationCode() + "-" + oldCmsDocId,
DocAuditStatus.TO_BE_AUDIT.getAuditCode());
cmsAsyncService.asyncUpdateBranchAndFileAndCreatePR(newDocWithAudit, cmsDocDto);
return newDocWithAudit.getId();
}
}
// 仓库改动待审核记录继续在当前记录上更新
oldCmsDoc.setAuditStatus(DocAuditStatus.TO_BE_AUDIT.getAuditCode());
newDocWithAudit = oldCmsDoc;
} else {
} else
//更新已发布文章
if (oldCmsDoc.getAuditStatus().equals(DocAuditStatus.AUDIT_PASS.getAuditCode())) {
//1.创建一条auditType="update-原文章id"audit_status为2其余字段与待更新文章一致的新文章数据
@ -914,9 +777,8 @@ public class CmsDocServiceImpl implements ICmsDocService {
DocAndZoneOperation.OPERATION_UPDATE.getOperationCode() + "-" + id,
DocAuditStatus.TO_BE_AUDIT.getAuditCode());
}
}
cmsAsyncService.asyncUpdateBranchAndFileAndCreatePR(newDocWithAudit, cmsDocDto);
return newDocWithAudit.getId();
return true;
}
}
@ -968,7 +830,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
private void deleteTmpBranchIfBranchExist(CmsDoc cmsDoc) {
CmsProject cmsProject = cmsProjectService.selectCmsProjectByDeptId(cmsDoc.getDeptId());
try {
gitLinkRequestHelper.doPost(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()), null);
gitLinkRequestHelper.doDelete(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()));
} catch (ServiceException e) {
//忽略分支不存在异常
if (!"分支不存在!".equals(e.getMessage())) {
@ -999,29 +861,15 @@ public class CmsDocServiceImpl implements ICmsDocService {
}
private CmsDoc insertDocWithAudit(CmsDoc oldCmsDoc, String auditType, String auditStatus) {
List<CmsDoc> tmpDocList = getTmpDocList(oldCmsDoc);
if (!tmpDocList.isEmpty()) {
throw new ServiceException("已存在待审核操作,请等待审核完成后再进行操作");
}
oldCmsDoc.setId(null);
oldCmsDoc.setAuditType(auditType);
oldCmsDoc.setAuditStatus(auditStatus);
oldCmsDoc.setCreateBy(SecurityUtils.getUsername());
// repo 改动显式设置 auditTmpBranchName null没有对应的分支
if (auditType != null && auditType.contains(DocAndZoneOperation.OPERATION_NON_REPO_UPDATE.getOperationCode())) {
oldCmsDoc.setAuditTmpBranchName(null);
}
cmsDocMapper.insertCmsDoc(oldCmsDoc);
return oldCmsDoc;
}
private CmsDoc insertDeletedDocWithAudit(CmsDoc oldCmsDoc, Long oldCmsDocId) {
List<CmsDoc> tmpDocList = getTmpDocList(oldCmsDoc);
if (!tmpDocList.isEmpty()) {
throw new ServiceException("已存在待审核操作,请等待审核完成后再进行操作");
}
oldCmsDoc.setId(null);
oldCmsDoc.setAuditType(DocAndZoneOperation.OPERATION_DELETE.getOperationCode() + "-" + oldCmsDocId);
oldCmsDoc.setAuditStatus(DocAuditStatus.TO_BE_AUDIT.getAuditCode());
@ -1167,13 +1015,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
visits = visits + cmsDoc.getVisits();
}
cmsDoc.setVisits(visits);
int rows = cmsDocMapper.updateCmsDoc(cmsDoc);
if (rows > 0) {
cmsAsyncService.addVisitsDocPointsAsync(cmsDoc.getDeptId(), cmsDoc.getCreateBy(),
cmsDoc.getId(), cmsDoc.getName(), cmsDoc.getVisits());
return true;
}
return false;
return cmsDocMapper.updateCmsDoc(cmsDoc) > 0;
}
@Override
@ -1297,9 +1139,6 @@ public class CmsDocServiceImpl implements ICmsDocService {
//首页推荐时需将顺序设置为1
setCmsSort(1, cmsDoc, false, null);
cmsDoc.setIsHomepage(CmsConstants.TRUE);
// 文章被设为精选异步增加作者积分
cmsAsyncService.addFeaturedDocPointsAsync(cmsDoc.getDeptId(), cmsDoc.getCreateBy(),
cmsDoc.getId(), cmsDoc.getName());
}
return cmsDocMapper.updateCmsDoc(cmsDoc);
}
@ -1323,41 +1162,6 @@ public class CmsDocServiceImpl implements ICmsDocService {
return cmsDocBaseDtoList;
}
@Override
public List<CmsDocBaseDto> selectHomePageDocMergeList(Long zoneId, List<Long> topDocIds) {
Long deptId = getDeptIdByZoneId(zoneId);
Set<Long> addedDocIds = new HashSet<>();
// 1. 获取首页推荐文章列表
List<CmsDocBaseDto> homePageDocs = selectHomePageDocList(deptId);
List<CmsDocBaseDto> result = new ArrayList<>();
// 2. 批量查询置顶文章完整数据按传入顺序放入结果头部
if (topDocIds != null && !topDocIds.isEmpty()) {
List<CmsDoc> topCmsDocs = cmsDocMapper.selectCmsDocByIds(topDocIds);
Map<Long, CmsDoc> topDocMap = new HashMap<>();
for (CmsDoc doc : topCmsDocs) {
topDocMap.put(doc.getId(), doc);
}
for (Long docId : topDocIds) {
CmsDoc doc = topDocMap.get(docId);
if (doc != null) {
result.add(doc.copyToCmsDocBaseDto());
addedDocIds.add(docId);
}
}
}
// 3. 追加首页推荐文章跳过已在置顶列表中的
for (CmsDocBaseDto doc : homePageDocs) {
if (!addedDocIds.contains(doc.getId())) {
result.add(doc);
}
}
return result;
}
@Override
@Transactional(rollbackFor = Exception.class)
public CmsDir initCmsDir(Long deptId, CmsDir cmsDirInput) {
@ -1504,17 +1308,6 @@ public class CmsDocServiceImpl implements ICmsDocService {
cmsAsyncService.updateCmsPublishDataByGitLink(cmsProject, cmsDoc, branchName);
}
private int deleteCmsDir(CmsProject cmsProject, CmsDoc cmsDir) {
if (!isNoAuditRequired(cmsDir.getDeptId()) || tokenService.hasDeptPerms(cmsDir.getDeptId(), UserConstants.CMS_PERMS_DOCS_AUDIT)) {
boolean deleteResult = cmsAsyncService.deleteCmsDocForGitLink(cmsDir, cmsProject, CmsConstants.BRANCH_MASTER);
if (deleteResult) {
return cmsDocMapper.deleteCmsDocById(cmsDir.getId());
}
throw new ServiceException("栏目删除失败");
}
throw new ServiceException("无权限删除栏目");
}
@Override
public int deleteCmsDocById(Long id) {
CmsDoc cmsDoc = cmsDocMapper.selectCmsDocById(id);
@ -1524,42 +1317,22 @@ public class CmsDocServiceImpl implements ICmsDocService {
checkCmsLockStatus(cmsDoc.getDeptId());
//2获取专区项目
CmsProject cmsProject = cmsProjectService.selectCmsProjectByDeptId(cmsDoc.getDeptId());
if (cmsDoc.getIsDir()) {
return deleteCmsDir(cmsProject, cmsDoc);
}
//删除待审核审核未通过文章删除临时分支拒绝合并请求
if (DocAuditStatus.TO_BE_AUDIT.getAuditCode().equals(cmsDoc.getAuditStatus())) {
// 非仓库改动类型直接删除
if (cmsDoc.getAuditType().contains(DocAndZoneOperation.OPERATION_NON_REPO_UPDATE.getOperationCode())) {
return cmsDocMapper.deleteCmsDocById(cmsDoc.getId());
}
//拒绝GitLink合并请求
String gitLinkPullRequestNumber = cmsDoc.getAuditPrNumber().split("-")[0];
gitLinkRequestHelper.doPost(
CmsGitLinkRequestUrl.REFUSE_MERGE_PULL_REQUEST(cmsProject, gitLinkPullRequestNumber), null);
gitLinkRequestHelper.doPost(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()), null);
return cmsDocMapper.deleteCmsDocById(cmsDoc.getId());
if (DocAuditStatus.TO_BE_AUDIT.getAuditCode().equals(cmsDoc.getAuditStatus())
|| DocAuditStatus.AUDIT_FAIL.getAuditCode().equals(cmsDoc.getAuditStatus())) {
gitLinkRequestHelper.doDelete(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()));
cmsDocMapper.deleteCmsDocById(id);
return 1;
}
if (DocAuditStatus.AUDIT_FAIL.getAuditCode().equals(cmsDoc.getAuditStatus())) {
return cmsDocMapper.deleteCmsDocById(cmsDoc.getId());
}
// 判读当前用户是否拥有文章直接删除权限
if (tokenService.hasDeptPerms(cmsDoc.getDeptId(), UserConstants.CMS_PERMS_DOCS_AUDIT) //当前用户为专区管理员
if (isCurrentUserZoneAdmin(cmsDoc.getDeptId()) //当前用户为专区管理员
|| isNoAuditRequired(cmsDoc.getDeptId()) //当前专区文章发布无需审核机制
) {
String branchName = CmsConstants.BRANCH_MASTER;
boolean deleteResult = cmsAsyncService.deleteCmsDocForGitLink(cmsDoc, cmsProject, branchName);
if (deleteResult) {
// 拒绝相关合并请求并清理所有临时分支同时删除所有临时申请
List<CmsDoc> tmpDocList = getTmpDocList(cmsDoc);
cmsAsyncService.clearTmpDoc(cmsProject, tmpDocList);
cmsDoc.setAuditStatus(DocAuditStatus.AUDIT_FAIL.getAuditCode());
cmsDoc.setRemark("管理员删除");
return cmsDocMapper.updateCmsDoc(cmsDoc);
return deleteCmsDocDatabaseById(cmsDoc.getId());
}
} else {
//1.创建一条auditType="delete-原文章id",audit_status=2其余字段与待删除文章一致的新文章数据
CmsDoc newDocWithAudit = insertDeletedDocWithAudit(cmsDoc, id);
@ -1569,18 +1342,6 @@ public class CmsDocServiceImpl implements ICmsDocService {
throw new ServiceException("文件删除失败");
}
@Override
public List<CmsDoc> getTmpDocList(CmsDoc cmsDoc) {
CmsDocSearchVo cmsDocSearchVo = new CmsDocSearchVo();
cmsDocSearchVo.setName(cmsDoc.getName());
cmsDocSearchVo.setDeptId(cmsDoc.getDeptId());
cmsDocSearchVo.setIsDir(false);
cmsDocSearchVo.setDirName(cmsDoc.getDirName());
cmsDocSearchVo.setAuditStatus(DocAuditStatus.TO_BE_AUDIT.getAuditCode());
// 拒绝相关合并请求并清理所有临时分支同时删除所有临时申请
return cmsDocMapper.selectCmsDocList(cmsDocSearchVo);
}
public int deleteCmsDocDatabaseById(Long id) {
CmsDoc cmsDoc = cmsDocMapper.selectCmsDocById(id);
if (cmsDoc != null && !cmsDoc.getIsDir()) {

View File

@ -49,16 +49,6 @@ public class CmsProjectController extends BaseController {
return R.ok(changeRoleResult);
}
@InnerAuth
@PutMapping("/changeOrgRole/user/{userId}/operate/{operate}")
@ApiOperation(value = "修改用户所在GItLink组织中的权限", hidden = true)
public R<Boolean> changeUserOrgRole(@PathVariable("userId") Long userId, @PathVariable("operate") Long operate
) {
Boolean changeRoleResult = cmsProjectService.changeUserOrgRole(userId, operate);
return R.ok(changeRoleResult);
}
/**
* 查询专区对应项目列表
*/

View File

@ -1,21 +0,0 @@
package com.microservices.cms.project.domain;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* @author otto
*/
@Data
public class GitlinkOrganizationTeamVo implements Serializable {
@ApiModelProperty(value = "团队权限")
private String authorize;
@ApiModelProperty(value = "团队Id")
private Long id;
@ApiModelProperty(value = "团队名称")
private String name;
@ApiModelProperty(value = "团队昵称")
private String nickname;
}

View File

@ -2,7 +2,6 @@ package com.microservices.cms.project.mapper;
import com.microservices.cms.project.domain.CmsProject;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@ -65,25 +64,4 @@ public interface CmsProjectMapper {
CmsProject selectCmsProjectByDeptId(Long deptId);
CmsProject selectCmsProjectByIdentifier(String identifier);
/**
* 全局搜索 - 全文索引查询
*
* @param keyword 搜索关键词
* @param deptId 组织ID可选
* @param offset 偏移量
* @param pageSize 每页数量
* @return 搜索结果列表
*/
List<CmsProject> globalSearch(@Param("keyword") String keyword, @Param("deptId") Long deptId,
@Param("offset") int offset, @Param("pageSize") int pageSize);
/**
* 全局搜索结果计数
*
* @param keyword 搜索关键词
* @param deptId 组织ID可选
* @return 结果数量
*/
Long globalSearchCount(@Param("keyword") String keyword, @Param("deptId") Long deptId);
}

View File

@ -82,6 +82,4 @@ public interface ICmsProjectService {
boolean initZoneCmsProjectByDept(Long deptId);
CmsProject selectCmsProjectByDeptIdAndNoException(Long deptId);
Boolean changeUserOrgRole(Long userId, Long operate);
}

View File

@ -6,7 +6,6 @@ import com.microservices.cms.doc.domain.*;
import com.microservices.cms.doc.service.ICmsAsyncService;
import com.microservices.cms.doc.service.ICmsDocService;
import com.microservices.cms.project.domain.CmsProject;
import com.microservices.cms.project.domain.GitlinkOrganizationTeamVo;
import com.microservices.cms.project.mapper.CmsProjectMapper;
import com.microservices.cms.project.service.ICmsProjectService;
import com.microservices.cms.utils.CmsConstants;
@ -321,40 +320,6 @@ public class CmsProjectServiceImpl implements ICmsProjectService {
return cmsProjectMapper.selectCmsProjectByDeptId(deptId);
}
@Override
public Boolean changeUserOrgRole(Long userId, Long operate) {
Long gitlinkOrgId = getOrganizationId();
SysUser sysUser = FeignUtils.getReturnData(remoteUserService.getSysUserByUserId(userId, SecurityConstants.INNER));
if (sysUser == null) {
return false;
}
List<GitlinkOrganizationTeamVo> gitlinkOrganizationTeamVoList = gitLinkRequestHelper.getAllDataByPage(
CmsGitLinkRequestUrl.TEAMS_BY_ORGANIZATION_URL(gitlinkOrgId), "teams", GitlinkOrganizationTeamVo.class
);
GitlinkOrganizationTeamVo ownerTeam = gitlinkOrganizationTeamVoList.stream().filter(x -> "owner".equals(x.getAuthorize())).findFirst().orElse(null);
if (ownerTeam == null) {
return false;
}
if (operate == 1) {
JSONObject addUserToTeam = new JSONObject();
addUserToTeam.put("username", sysUser.getUserName());
gitLinkRequestHelper.doPost(
CmsGitLinkRequestUrl.ADD_USER_TO_ORGANIZATION_TEAM(gitLinkOrganization, ownerTeam.getId()), addUserToTeam);
}
if (operate == 3) {
try {
gitLinkRequestHelper.doDelete(CmsGitLinkRequestUrl.DELETE_USER_FROM_ORGANIZATION_TEAM(
gitLinkOrganization,
ownerTeam.getId(),
sysUser.getUserName()
));
} catch (Exception e) {
return false;
}
}
return false;
}
@Override
public Boolean changeUserRole(Long userId, Long oldDeptId, Long newDeptId, Long oldRoleId, Long newRoleId) {
SysUser sysUser = FeignUtils.getReturnData(remoteUserService.getSysUserByUserId(userId, SecurityConstants.INNER));
@ -373,10 +338,9 @@ public class CmsProjectServiceImpl implements ICmsProjectService {
try {
gitLinkRequestHelper.doDelete(CmsGitLinkRequestUrl.REMOVE_USER_FROM_PROJECT(oldCmsProject), requestBody);
} catch (ServiceException e) {
log.error("将用户从原项目中移除发生异常:{}", e.getMessage());
// if (!e.getMessage().contains("用户为组织成员,请到组织下操作")||!e.getMessage().contains("还不是项目成员")) {
// throw new ServiceException(e.getMessage());
// }
if (!e.getMessage().contains("用户为组织成员,请到组织下操作")) {
throw new ServiceException(e.getMessage());
}
}
}
//将用户加入新的项目

View File

@ -1,43 +0,0 @@
package com.microservices.cms.search.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* 文章搜索请求对象
*
* @author microservices
*/
@Data
@ApiModel("文章搜索请求对象")
public class DocSearchRequest implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 搜索关键词
*/
@ApiModelProperty(value = "搜索关键词", required = true)
private String keyword;
/**
* 专区ID
*/
@ApiModelProperty(value = "专区ID")
private Long zoneId;
/**
* 页码
*/
@ApiModelProperty(value = "页码")
private Integer pageNum = 1;
/**
* 每页数量
*/
@ApiModelProperty(value = "每页数量")
private Integer pageSize = 20;
}

View File

@ -1,63 +0,0 @@
package com.microservices.cms.search.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
/**
* 全局搜索响应对象
*
* @author microservices
*/
@Data
@ApiModel("文章搜索响应对象")
public class DocSearchResponse implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 总结果数
*/
@ApiModelProperty(value = "总结果数")
private Long total;
/**
* 搜索结果列表
*/
@ApiModelProperty(value = "搜索结果列表")
private List<DocSearchResult> results;
/**
* 各类型结果数量统计
*/
@ApiModelProperty(value = "各类型结果数量统计")
private Map<String, Long> typeCounts;
/**
* 搜索耗时毫秒
*/
@ApiModelProperty(value = "搜索耗时(毫秒)")
private Long searchTime;
/**
* 搜索关键词
*/
@ApiModelProperty(value = "搜索关键词")
private String keyword;
/**
* 当前页码
*/
@ApiModelProperty(value = "当前页码")
private Integer pageNum;
/**
* 每页数量
*/
@ApiModelProperty(value = "每页数量")
private Integer pageSize;
}

View File

@ -1,65 +0,0 @@
package com.microservices.cms.search.dto;
import com.alibaba.fastjson2.JSONObject;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 文章搜索结果对象
*
* @author microservices
*/
@Data
@ApiModel("文章搜索结果对象")
public class DocSearchResult implements Serializable {
private static final long serialVersionUID = 1L;
/**
* ID
*/
@ApiModelProperty(value = "ID")
private Long id;
/**
* 标题
*/
@ApiModelProperty(value = "标题")
private String title;
/**
* 内容摘要
*/
@ApiModelProperty(value = "内容摘要")
private String content;
/**
* 组织ID
*/
@ApiModelProperty(value = "组织ID")
private Long deptId;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@ApiModelProperty(value = "创建时间")
private Date createTime;
/**
* 相关度评分
*/
@ApiModelProperty(value = "相关度评分")
private Double score;
/**
* 扩展数据JSON格式
*/
@ApiModelProperty(value = "扩展数据")
private JSONObject extendData;
}

View File

@ -1,20 +0,0 @@
package com.microservices.cms.search.service;
import com.microservices.cms.search.dto.DocSearchRequest;
import com.microservices.cms.search.dto.DocSearchResponse;
/**
* 全局搜索服务接口
*
* @author microservices
*/
public interface IGlobalSearchService {
/**
* 全局搜索
*
* @param request 搜索请求
* @return 搜索响应
*/
DocSearchResponse docSearch(DocSearchRequest request);
}

View File

@ -1,215 +0,0 @@
package com.microservices.cms.search.service.impl;
import com.alibaba.fastjson2.JSONObject;
import com.microservices.cms.doc.domain.CmsDoc;
import com.microservices.cms.doc.domain.CmsDocWithScore;
import com.microservices.cms.doc.mapper.CmsDocMapper;
import com.microservices.cms.doc.service.ICmsDocService;
import com.microservices.cms.search.service.IGlobalSearchService;
import com.microservices.common.core.utils.StringUtils;
import com.microservices.common.redis.service.RedisService;
import com.microservices.cms.search.dto.DocSearchRequest;
import com.microservices.cms.search.dto.DocSearchResponse;
import com.microservices.cms.search.dto.DocSearchResult;
import com.microservices.common.security.utils.SecurityUtils;
import com.microservices.system.api.model.LoginUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
/**
* 全局搜索服务实现
*
* @author microservices
*/
@Service
public class GlobalSearchServiceImpl implements IGlobalSearchService {
private static final Logger logger = LoggerFactory.getLogger(GlobalSearchServiceImpl.class);
@Autowired
private CmsDocMapper cmsDocMapper;
@Autowired
private RedisService redisService;
@Autowired
private ICmsDocService cmsDocService;
private static final String SEARCH_CACHE_PREFIX = "search:cache:";
private static final Long CACHE_EXPIRE_SECONDS = 300L; // 5分钟
@Override
public DocSearchResponse docSearch(DocSearchRequest request) {
long startTime = System.currentTimeMillis();
// 参数校验
if (StringUtils.isEmpty(request.getKeyword())) {
return buildEmptyResponse(request);
}
// 1. 尝试从缓存获取仅第一页
if (request.getPageNum() == 1) {
String cacheKey = buildCacheKey(request);
DocSearchResponse cached = redisService.getCacheObject(SEARCH_CACHE_PREFIX + cacheKey);
if (cached != null) {
cached.setSearchTime(System.currentTimeMillis() - startTime);
return cached;
}
}
// 2. 并行搜索各类型
List<DocSearchResult> allResults = new ArrayList<>();
Map<String, Long> typeCounts = new HashMap<>();
// 获取当前用户信息
LoginUser loginUser = SecurityUtils.getLoginUser();
// 搜索CMS文章
List<DocSearchResult> docResults = searchDocs(request);
allResults.addAll(docResults);
typeCounts.put("doc", (long) docResults.size());
// 3. 按相关度评分排序
allResults.sort((a, b) -> {
if (b.getScore() != null && a.getScore() != null) {
return b.getScore().compareTo(a.getScore());
}
return 0;
});
// 4. 分页
int total = allResults.size();
int start = (request.getPageNum() - 1) * request.getPageSize();
int end = Math.min(start + request.getPageSize(), total);
List<DocSearchResult> pagedResults = start < total ?
allResults.subList(start, end) : new ArrayList<>();
// 5. 构建响应
DocSearchResponse response = new DocSearchResponse();
response.setTotal((long) total);
response.setResults(pagedResults);
response.setTypeCounts(typeCounts);
response.setKeyword(request.getKeyword());
response.setPageNum(request.getPageNum());
response.setPageSize(request.getPageSize());
response.setSearchTime(System.currentTimeMillis() - startTime);
// 6. 缓存结果仅第一页
if (request.getPageNum() == 1) {
String cacheKey = buildCacheKey(request);
redisService.setCacheObject(SEARCH_CACHE_PREFIX + cacheKey, response, CACHE_EXPIRE_SECONDS, TimeUnit.SECONDS);
}
// 7. 记录搜索日志
recordSearchLog(request, response, loginUser);
return response;
}
/**
* 记录搜索日志
*/
private void recordSearchLog(DocSearchRequest request, DocSearchResponse response, LoginUser loginUser) {
Long userId = loginUser != null ? loginUser.getUserId() : null;
String searchTypes = "文章";
Long deptId = cmsDocService.getDeptIdByZoneId(request.getZoneId());
logger.info("全局搜索 - 关键词: {}, 用户ID: {}, 耗时: {}ms, 结果数: {}, 类型: {}, 专区Id: {}, 组织ID: {}",
request.getKeyword(),
userId,
response.getSearchTime(),
response.getTotal(),
searchTypes,
request.getZoneId(),
deptId
);
}
/**
* 搜索CMS文章
*/
private List<DocSearchResult> searchDocs(DocSearchRequest request) {
List<DocSearchResult> results = new ArrayList<>();
try {
// 使用部门ID参数
Long deptId = cmsDocService.getDeptIdByZoneId(request.getZoneId());
// 使用新的带评分的查询方法
List<CmsDocWithScore> docs = cmsDocMapper.globalSearchWithScore(
request.getKeyword(),
deptId,
(request.getPageNum() - 1) * request.getPageSize(),
request.getPageSize()
);
for (CmsDocWithScore doc : docs) {
DocSearchResult result = new DocSearchResult();
result.setId(doc.getId());
result.setTitle(highlightKeyword(doc.getName(), request.getKeyword()));
result.setContent(highlightKeyword(doc.getSummary(), request.getKeyword()));
result.setDeptId(doc.getDeptId());
result.setCreateTime(doc.getCreateTime());
// 使用数据库返回的实际搜索评分
Double score = doc.getSearchScore();
if (score == null || score == 0.0) {
// 对于 LIKE 匹配无全文评分赋予最小评分
score = 0.1;
}
result.setScore(score);
JSONObject extendData = new JSONObject();
extendData.put("visits", doc.getVisits());
extendData.put("publishTime", doc.getPublishTime());
result.setExtendData(extendData);
results.add(result);
}
} catch (Exception e) {
// 记录错误但继续处理其他类型
System.err.println("搜索CMS文章失败: " + e.getMessage());
}
return results;
}
/**
* 构建缓存键
*/
private String buildCacheKey(DocSearchRequest request) {
Long deptId = cmsDocService.getDeptIdByZoneId(request.getZoneId());
StringBuilder key = new StringBuilder(request.getKeyword());
key.append(":").append(String.join(",", "Doc"));
if (deptId != null) {
key.append(":dept=").append(deptId);
}
return key.toString();
}
/**
* 构建空响应
*/
private DocSearchResponse buildEmptyResponse(DocSearchRequest request) {
DocSearchResponse response = new DocSearchResponse();
response.setTotal(0L);
response.setResults(new ArrayList<>());
response.setTypeCounts(new HashMap<>());
response.setKeyword(request.getKeyword());
response.setPageNum(request.getPageNum());
response.setPageSize(request.getPageSize());
response.setSearchTime(0L);
return response;
}
/**
* 将文本中的关键词用 <em> 标签包裹不区分大小写
*/
private String highlightKeyword(String text, String keyword) {
if (StringUtils.isEmpty(text) || StringUtils.isEmpty(keyword)) {
return text;
}
return text.replaceAll("(?i)(" + Pattern.quote(keyword) + ")", "<em>$1</em>");
}
}

View File

@ -25,24 +25,4 @@ public class CmsConstants {
*
*/
public final static String TRUE = "1";
/**
* 专区积分异步队列 Redis Key Zone 模块保持一致
*/
public final static String ZONE_POINTS_ASYNC_QUEUE_KEY = "zone:points:async:queue";
/**
* 发布文章积分类型
*/
public final static String PUBLISH_DOC = "publishDoc";
/**
* 浏览文章积分类型
*/
public final static String VISITS_DOC = "visitsDoc";
/**
* 精选文章积分类型
*/
public final static String FEATURED_DOC = "featuredDoc";
}

View File

@ -12,18 +12,6 @@ import java.util.Map;
public class CmsGitLinkRequestUrl extends GitLinkRequestUrl {
public static GitLinkRequestUrl ADD_USER_TO_ORGANIZATION_TEAM(String orgIdentifier, Long teamId) {
return getAdminGitLinkRequestUrl(String.format("/api/organizations/%s/teams/%d/team_users.json", orgIdentifier, teamId));
}
public static String TEAMS_BY_ORGANIZATION_URL(Long orgId) {
return String.format("/api/organizations/%s/teams.json", orgId);
}
public static GitLinkRequestUrl DELETE_USER_FROM_ORGANIZATION_TEAM(String orgIdentifier, Long teamId, String username) {
return getAdminGitLinkRequestUrl(String.format("/api/organizations/%s/teams/%d/team_users/%s.json", orgIdentifier, teamId, username));
}
/**
* 创建项目
*/
@ -78,7 +66,7 @@ public class CmsGitLinkRequestUrl extends GitLinkRequestUrl {
public static GitLinkRequestUrl DELETE_BRANCH(CmsProject cmsProject, String branchName) {
return getGitLinkRequestUrl(
String.format("/api/v1/%s/%s/branches/delete.json?branch_name=%s", cmsProject.getUsername(), cmsProject.getIdentifier(), branchName)
String.format("/api/v1/%s/%s/branches/%s.json", cmsProject.getUsername(), cmsProject.getIdentifier(), branchName)
);
}
@ -88,12 +76,6 @@ public class CmsGitLinkRequestUrl extends GitLinkRequestUrl {
);
}
public static GitLinkRequestUrl REOPEN_PULL_REQUEST(CmsProject cmsProject, String giteaPrNumber) {
return getGitLinkRequestUrl(
String.format("/api/v1/%s/%s/pulls/%s/reopen", cmsProject.getUsername(), cmsProject.getIdentifier(), giteaPrNumber)
);
}
public static GitLinkRequestUrl GET_PULL_REQUEST_DETAILS(CmsProject cmsProject, String giteaPrNumber) {
return getGitLinkRequestUrl(
String.format("/api/%s/%s/pulls/%s.json", cmsProject.getUsername(), cmsProject.getIdentifier(), giteaPrNumber)
@ -119,7 +101,7 @@ public class CmsGitLinkRequestUrl extends GitLinkRequestUrl {
}
public static GitLinkRequestUrl ADD_USER_FROM_PROJECT(CmsProject cmsProject) {
return getAdminGitLinkRequestUrl(
return getGitLinkRequestUrl(
String.format("/api/%s/%s/collaborators.json", cmsProject.getUsername(), cmsProject.getIdentifier())
);
}

View File

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

View File

@ -37,7 +37,6 @@
<result property="auditTmpBranchName" column="audit_tmp_branch_name"/>
<result property="auditPrNumber" column="audit_pr_number"/>
<result property="keywords" column="keywords"/>
<result property="remark" column="remark"/>
</resultMap>
<sql id="selectCmsDocVo">
@ -72,8 +71,7 @@
audit_type,
audit_tmp_branch_name,
keywords,
audit_pr_number,
remark
audit_pr_number
from cms_doc
</sql>
@ -105,51 +103,7 @@
cd.is_homepage,
cd.editor_type,
cd.keywords,
cd.is_dir,
cd.audit_status,
cd.audit_type,
cd.audit_tmp_branch_name,
cd.audit_pr_number,
cd.remark
from cms_doc cd
</sql>
<sql id="selectCmsDocWithScoreVo">
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,
cd.head_img,
cd.dir_name,
cd.dept_id,
cd.issue_id,
cd.is_homepage,
cd.editor_type,
cd.keywords,
cd.is_dir,
cd.audit_status,
cd.audit_type,
cd.audit_tmp_branch_name,
cd.audit_pr_number,
-- 计算全文搜索相关度评分
MATCH(cd.name, cd.summary) AGAINST(#{keyword} IN NATURAL LANGUAGE MODE) AS search_score,
cd.remark
cd.is_dir
from cms_doc cd
</sql>
@ -187,14 +141,9 @@
cd.is_homepage,
cd.editor_type,
cd.keywords,
cd.is_dir,
cd.audit_status,
cd.audit_type,
cd.audit_tmp_branch_name,
cd.audit_pr_number,
cd.remark
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 and dir.is_dir=1
left join cms_doc dir on cd.dir_name = dir.name and cd.dept_id = dir.dept_id
</sql>
<select id="selectCmsDocAndDirListByDept" resultMap="CmsDocResult">
@ -217,19 +166,10 @@
where id = #{id}
</select>
<select id="selectCmsDocByIds" resultMap="CmsDocResult">
<include refid="selectCmsDocVo"/>
where id in
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
<select id="selectCmsDocByNameAndDeptId" resultMap="CmsDocResult">
<include refid="selectCmsDocVo"/>
<where>
and name = #{name}
and audit_status != 0
and dept_id = #{deptId}
<choose>
<when test="dirName != null">
@ -241,10 +181,6 @@
</choose>
</where>
</select>
<select id="selectCmsDocByNameAndDeptIdIgnoreDirName" resultMap="CmsDocResult">
<include refid="selectCmsDocVo"/>
where name = #{name} and dept_id = #{deptId} and is_dir = 0
</select>
<select id="selectCmsDocDirByNameAndDeptId" resultMap="CmsDocResult">
<include refid="selectCmsDocVo"/>
where name = #{name} and dept_id = #{deptId} and is_dir = 1
@ -285,7 +221,6 @@
<if test="auditStatus != null and auditStatus != '-1' ">and audit_status = #{auditStatus}</if>
<if test="auditStatus == null ">and audit_status = '1'</if>
<if test="createBy != null and createBy != '' ">and create_by = #{createBy}</if>
<if test="excludeId != null ">and id != #{excludeId}</if>
</where>
</sql>
@ -523,7 +458,6 @@
<if test="auditTmpBranchName != null">audit_tmp_branch_name = #{auditTmpBranchName},</if>
<if test="auditPrNumber != null">audit_pr_number = #{auditPrNumber},</if>
<if test="keywords != null">keywords = #{keywords},</if>
<if test="remark != null">remark = #{remark},</if>
</trim>
where id = #{id}
</update>
@ -560,70 +494,4 @@
#{id}
</foreach>
</delete>
<!-- 全局搜索 - 全文索引查询(含 LIKE 后备搜索) -->
<select id="globalSearch" resultMap="CmsDocResult">
<include refid="selectCmsDocAliasVo"/>
WHERE
cd.is_dir = 0
AND cd.audit_status = '1'
AND (
MATCH(cd.name, cd.summary) AGAINST(#{keyword} IN BOOLEAN MODE)
OR cd.name LIKE CONCAT('%', #{keyword}, '%')
OR cd.summary LIKE CONCAT('%', #{keyword}, '%')
)
<if test="deptId != null">
AND cd.dept_id = #{deptId}
</if>
ORDER BY
CASE
WHEN MATCH(cd.name, cd.summary) AGAINST(#{keyword} IN BOOLEAN MODE) > 0 THEN 1
ELSE 2
END,
cd.update_time DESC
LIMIT #{offset}, #{pageSize}
</select>
<select id="globalSearchCount" resultType="java.lang.Long">
SELECT COUNT(*)
FROM cms_doc cd
WHERE
cd.is_dir = 0
AND cd.audit_status = '1'
AND (
MATCH(cd.name, cd.summary) AGAINST(#{keyword} IN BOOLEAN MODE)
OR cd.name LIKE CONCAT('%', #{keyword}, '%')
OR cd.summary LIKE CONCAT('%', #{keyword}, '%')
)
<if test="deptId != null">
AND cd.dept_id = #{deptId}
</if>
</select>
<!-- ResultMap for CmsDocWithScore -->
<resultMap type="com.microservices.cms.doc.domain.CmsDocWithScore" id="CmsDocWithScoreResult" extends="CmsDocResult">
<result property="searchScore" column="search_score"/>
</resultMap>
<!-- 全局搜索 - 带评分的全文索引查询 -->
<select id="globalSearchWithScore" resultMap="CmsDocWithScoreResult">
<include refid="selectCmsDocWithScoreVo"/>
WHERE
cd.is_dir = 0
AND cd.audit_status = '1'
AND (
MATCH(cd.name, cd.summary) AGAINST(#{keyword} IN BOOLEAN MODE)
OR cd.name LIKE CONCAT('%', #{keyword}, '%')
OR cd.summary LIKE CONCAT('%', #{keyword}, '%')
)
<if test="deptId != null">
AND cd.dept_id = #{deptId}
</if>
ORDER BY
-- 主要排序:按相关度评分降序
search_score DESC,
-- 次要排序:更新时间降序
cd.update_time DESC
LIMIT #{offset}, #{pageSize}
</select>
</mapper>

View File

@ -159,38 +159,4 @@
item="id" collection="array" open="(" separator="," close=")">#{id}
</foreach>
</delete>
<!-- 全局搜索 - 全文索引查询 -->
<select id="globalSearch" resultMap="CmsProjectResult">
SELECT id,
name,
description,
user_id,
dept_id,
identifier,
username,
create_by,
create_time,
update_time
FROM cms_project cp
WHERE
MATCH(cp.name) AGAINST(#{keyword} IN NATURAL LANGUAGE MODE)
<if test="deptId != null">
AND cp.dept_id = #{deptId}
</if>
ORDER BY
MATCH(cp.name) AGAINST(#{keyword} IN NATURAL LANGUAGE MODE) DESC,
cp.update_time DESC
LIMIT #{offset}, #{pageSize}
</select>
<select id="globalSearchCount" resultType="java.lang.Long">
SELECT COUNT(*)
FROM cms_project cp
WHERE
MATCH(cp.name) AGAINST(#{keyword} IN NATURAL LANGUAGE MODE)
<if test="deptId != null">
AND cp.dept_id = #{deptId}
</if>
</select>
</mapper>

View File

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

View File

@ -9,14 +9,17 @@ import com.microservices.common.log.enums.BusinessType;
import com.microservices.dms.achievementLibrary.domain.AchQueryVo;
import com.microservices.dms.achievementLibrary.domain.Achievements;
import com.microservices.dms.achievementLibrary.service.IAchievementsService;
import com.microservices.system.api.RemoteFileService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
* 成果Controller
*
@ -30,13 +33,17 @@ public class AchievementsController extends BaseController {
@Autowired
private IAchievementsService achievementsService;
@Autowired
private RemoteFileService remoteFileService;
/**
* 查询成果列表
*/
@GetMapping("/list")
public TableDataInfo list(Achievements achievements) {
public TableDataInfo list(AchQueryVo achQueryVo) {
startPage();
List<Achievements> list = achievementsService.selectAchievementsList(achievements);
achQueryVo.setAllStatus(true);
List<Achievements> list = achievementsService.getAllResult(achQueryVo);
return getDataTable(list);
}
@ -45,8 +52,8 @@ public class AchievementsController extends BaseController {
*/
@Log(title = "成果", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, Achievements achievements) {
List<Achievements> list = achievementsService.selectAchievementsList(achievements);
public void export(HttpServletResponse response, AchQueryVo achQueryVo) {
List<Achievements> list = achievementsService.selectAchievementsList(achQueryVo);
ExcelUtil<Achievements> util = new ExcelUtil<Achievements>(Achievements.class);
util.exportExcel(response, list, "成果数据");
}
@ -56,7 +63,7 @@ public class AchievementsController extends BaseController {
*/
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id) {
return success(achievementsService.selectAchievementsById(id));
return success(achievementsService.selectAchievementsById(id, false));
}
/**
@ -215,19 +222,20 @@ public class AchievementsController extends BaseController {
public AjaxResult getWatchFavoriteStatusById(Long id, Long userId) {
return success(achievementsService.getWatchFavoriteStatusById(id, userId));
}
@ApiOperation("根据成果ID获取近7日成果点击数量折线图")
@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) {
return success(achievementsService.get7AddClickById(id,clickType));
return success(achievementsService.get7AddClickById(id, clickType));
}
@ApiOperation("根据专家id获取近7日成果点击数量折线图")
@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) {
return success(achievementsService.get7AddClickById(id,clickType));
return success(achievementsService.get7AddClickById(id, clickType));
}
@ApiOperation("根据成果ID,获取成果的相关行为数据统计(点击量、搜索量、附件下载量、收藏、关注)")
@ -243,21 +251,24 @@ public class AchievementsController extends BaseController {
return success(achievementsService.getAllAreas(areaName));
}
@ApiOperation("根据已有数据,获取融办名称数据")
@GetMapping("/getAllField2")
public AjaxResult getAllField2() {
return success(achievementsService.getAllField2());
}
@ApiOperation("根据领域,将成果数据进行分类")
@GetMapping("/getAreaStatistic")
public AjaxResult getAreaStatistic(String areaKey)
{
public AjaxResult getAreaStatistic(String areaKey) {
return success(achievementsService.getAreaStatistic(areaKey));
}
@ApiOperation("根据领域获取领域详情")
@GetMapping("/getAreaDetail")
public AjaxResult getAreaDetail(String areaKey,String kyxmSum,String source)
{
return success(achievementsService.getAreaDetail(areaKey,kyxmSum,source));
public AjaxResult getAreaDetail(String areaKey, String kyxmSum, String source) {
return success(achievementsService.getAreaDetail(areaKey, kyxmSum, source));
}
/**
@ -265,46 +276,59 @@ public class AchievementsController extends BaseController {
*/
@ApiOperation(value = "获取开源项目的相关成果")
@GetMapping("/getRelatedAch")
public AjaxResult getRelatedAch(Long id,Long sourceId)
{
return success(achievementsService.getRelatedAch(id,sourceId));
public AjaxResult getRelatedAch(Long id, Long sourceId) {
return success(achievementsService.getRelatedAch(id, sourceId));
}
@ApiOperation(value = "获取开源竞赛的相关成果")
@GetMapping("/getRelatedAchCompetition")
public AjaxResult getRelatedAchCompetition(Long id,Long sourceId)
{
return success(achievementsService.getRelatedAchCompetition(id,sourceId));
public AjaxResult getRelatedAchCompetition(Long id, Long sourceId) {
return success(achievementsService.getRelatedAchCompetition(id, sourceId));
}
@ApiOperation(value = "获取创客任务的相关成果")
@GetMapping("/getRelatedAchTask")
public AjaxResult getRelatedAchTask(Long id,Long sourceId)
{
return success(achievementsService.getRelatedAchTask(id,sourceId));
public AjaxResult getRelatedAchTask(Long id, Long sourceId) {
return success(achievementsService.getRelatedAchTask(id, sourceId));
}
@ApiOperation(value = "获取专家-创客任务的相关成果")
@GetMapping("/getRelatedAchTaskOfExpert")
public AjaxResult getRelatedAchTaskOfExpert(Long expertId)
{
public AjaxResult getRelatedAchTaskOfExpert(Long expertId) {
return success(achievementsService.getRelatedAchTaskOfExpert(expertId));
}
@ApiOperation(value = "获取专家-开源竞赛的相关成果")
@GetMapping("/getRelatedAchCompetitionOfExpert")
public AjaxResult getRelatedAchCompetitionOfExpert(Long expertId)
{
public AjaxResult getRelatedAchCompetitionOfExpert(Long expertId) {
return success(achievementsService.getRelatedAchCompetitionOfExpert(expertId));
}
@ApiOperation(value = "搜索行为数据记录")
@GetMapping("/getSearchResult")
public AjaxResult getSearchResult(String achName,Long userId)
{
return success(achievementsService.getSearchResult(achName,userId));
public AjaxResult getSearchResult(String achName, Long 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.log.annotation.Log;
import com.microservices.common.log.enums.BusinessType;
import com.microservices.common.security.utils.SecurityUtils;
import com.microservices.dms.achievementLibrary.domain.SchoolEnterpriseAchievements;
import com.microservices.dms.achievementLibrary.service.ISchoolEnterpriseAchievementsService;
import io.swagger.annotations.Api;
@ -15,9 +16,11 @@ import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;
/**
* 校企成果Controller
@ -90,6 +93,21 @@ public class SchoolEnterpriseAchievementsController extends BaseController
return AjaxResult.success(schoolEnterpriseAchievementsService.insertSchoolEnterpriseAchievements(schoolEnterpriseAchievements));
}
@PostMapping("/importData")
@Log(title = "校企成果", businessType = BusinessType.INSERT)
public AjaxResult importData(@RequestPart("file") MultipartFile file,
@RequestParam(value = "updateSupport", defaultValue = "false") boolean updateSupport) throws Exception {
ExcelUtil<SchoolEnterpriseAchievements> util = new ExcelUtil<>(SchoolEnterpriseAchievements.class);
// 1. 解析Excel获取实体列表和图片Map
List<SchoolEnterpriseAchievements> list = util.importExcelWithImages(StringUtils.EMPTY, file.getInputStream(), 0);
Map<String, MultipartFile> imageMap = util.getExcelImageMap();
// 2. 调用业务层处理传入实体列表和图片Map
String operName = SecurityUtils.getUsername();
String message = schoolEnterpriseAchievementsService.batchInsertSchoolEnterpriseAchievements(list, imageMap, updateSupport, operName);
return AjaxResult.success(message);
}
/**
* 修改校企成果
*/

View File

@ -1,12 +1,25 @@
package com.microservices.dms.achievementLibrary.domain;
import java.util.List;
public class AchQueryVo {
private String areaQuery;//领域
private String source;//来源
private String achievementType;//成果类型
private String achievementName;//名称
private Long userId;
private String userId;
private String showAchievementName;
private String rongbanName;
private String maturityLevel; //成果成熟度
private String sortBy; //排序字段
private String sortOrder; //升降序
private Boolean userSelf = false; //用户自己
private Boolean allStatus = false; //所有状态
private List<String> achievementTypeList;//成果类型
private List<String> sourceList;//成果类型
public String getAreaQuery() {
return areaQuery;
}
@ -31,11 +44,11 @@ public class AchQueryVo {
this.achievementName = achievementName;
}
public Long getUserId() {
public String getUserId() {
return userId;
}
public void setUserId(Long userId) {
public void setUserId(String userId) {
this.userId = userId;
}
@ -46,4 +59,77 @@ public class AchQueryVo {
public void setShowAchievementName(String showAchievementName) {
this.showAchievementName = showAchievementName;
}
public String getRongbanName() {
return rongbanName;
}
public void setRongbanName(String rongbanName) {
this.rongbanName = rongbanName;
}
public List<String> getAchievementTypeList() {
return achievementTypeList;
}
public void setAchievementTypeList(List<String> achievementTypeList) {
this.achievementTypeList = achievementTypeList;
}
public List<String> getSourceList() {
return sourceList;
}
public void setSourceList(List<String> sourceList) {
this.sourceList = sourceList;
}
public String getMaturityLevel() {
return maturityLevel;
}
public void setMaturityLevel(String maturityLevel) {
this.maturityLevel = maturityLevel;
}
public String getAchievementType() {
return achievementType;
}
public void setAchievementType(String achievementType) {
this.achievementType = achievementType;
}
public String getSortBy() {
return sortBy;
}
public void setSortBy(String sortBy) {
this.sortBy = sortBy;
}
public String getSortOrder() {
return sortOrder;
}
public void setSortOrder(String sortOrder) {
this.sortOrder = sortOrder;
}
public Boolean getUserSelf() {
return userSelf;
}
public void setUserSelf(Boolean userSelf) {
this.userSelf = userSelf;
}
public Boolean getAllStatus() {
return allStatus;
}
public void setAllStatus(Boolean allStatus) {
this.allStatus = allStatus;
}
}

View File

@ -0,0 +1,9 @@
package com.microservices.dms.achievementLibrary.domain;
import com.microservices.system.api.domain.SysDictData;
import lombok.Data;
@Data
public class AchievementSourceDictVo extends SysDictData {
private Long cnt;
}

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

@ -0,0 +1,9 @@
package com.microservices.dms.achievementLibrary.domain;
import com.microservices.system.api.domain.SysDictData;
import lombok.Data;
@Data
public class AchievementTypeDictVo extends SysDictData {
private Long cnt;
}

View File

@ -38,33 +38,37 @@ public class Achievements extends BaseEntity {
/**
* 成果领域1
*/
@Excel(name = "成果领域1")
// @Excel(
// name = "成果领域",
// readConverterExp = "1=理论研究,2=政策法规,3=医学,4=电子信息,5=通信工程,6=计算机科学,7=软件工程,9=人工智能,10=知识图谱,12=大数据,23=机器人,24=无人系统,25=创意征集,26=文献翻译,27=成果转化,28=科技协同,29=科研组织模式,30=新兴领域,31=其他,32=电子元器件,33=测试仪器,34=军事历史,35=计算机网络"
// )
@Excel(name = "成果领域一级")
private String field1;
//成果领域名称
private String field1Name;
/**
* 成果领域2
* 成果领域二级
*/
@Excel(name = "成果领域2")
@Excel(name = "成果领域二级")
private String field2;
/**
* 成果领域3
* 融办描述
*/
@Excel(name = "成果领域3")
@Excel(name = "融办描述")
private String field3;
/**
* 成果类型
*/
@Excel(name = "成果类型")
@Excel(name = "成果类型", readConverterExp = "1=基础研究成果,2=技术开发成果,3=应用研究成果,4=软科学成果")
private String achievementType;
/**
* 成果来源
*/
@Excel(name = "成果来源")
@Excel(name = "成果来源渠道", readConverterExp = "1=开源项目,2=创客任务,3=开放竞赛,4=校企成果,5=省市融办", defaultValue = "5")
private String source;
/**
@ -74,15 +78,15 @@ public class Achievements extends BaseEntity {
private Long sourceId;
/**
* 成果来源跳转链接
* 成果来源URL
*/
@Excel(name = "成果来源跳转链接")
@Excel(name = "成果来源URL")
private String sourceLink;
/**
* 成果标签
*/
@Excel(name = "成果标签")
@Excel(name = "成果标签", readConverterExp = "1=高校,2=企业,3=其他")
private String tags;
/**
@ -92,15 +96,15 @@ public class Achievements extends BaseEntity {
private String summary;
/**
* 发布单位
* 成果提供方
*/
@Excel(name = "发布单位")
@Excel(name = "成果提供方")
private String publishingUnit;
/**
* 成果地址
* 单位概况
*/
@Excel(name = "成果地址")
@Excel(name = "单位概况")
private String address;
/**
@ -116,9 +120,9 @@ public class Achievements extends BaseEntity {
private String contactPerson;
/**
* 联系电话
* 联系方式
*/
@Excel(name = "联系电话")
@Excel(name = "联系方式")
private String contactNumber;
/**
@ -140,9 +144,9 @@ public class Achievements extends BaseEntity {
private String status;
/**
* 成果详情
* 成果简介
*/
@Excel(name = "成果详情")
@Excel(name = "成果简介")
private String details;
/**
@ -167,7 +171,7 @@ public class Achievements extends BaseEntity {
/**
* 成果图片
*/
@Excel(name = "成果图片")
@Excel(name = "图片")
private String images;
/**
@ -176,20 +180,32 @@ public class Achievements extends BaseEntity {
@Excel(name = "成果附件")
private String attachments;
//成果小类
@Excel(name = "成果图片视频")
private String ext1;
//成果状态
@Excel(name = "应用图片视频")
private String ext2;
//成果团队名称
@Excel(name = "应用场景")
private String ext3;
private String ext4;
private String ext5;
@Excel(name = "成果载体")
private String storageType;
@Excel(name = "成果成熟度")
private String maturityLevel;
@Excel(name = "推介范围")
private String recommendScope;
private Long clickSum;
private Long watcherSum;
private Long favoriteSum;
private Long dockSum;
private String filedDesc;
private Float hotRank;
private Long attachmentCount;
private int attachmentCount;
private Integer isExpertAudit;
private String userImg;
private String userNickName;
@ -221,6 +237,22 @@ public class Achievements extends BaseEntity {
this.ext3 = ext3;
}
public String getExt4() {
return ext4;
}
public void setExt4(String ext4) {
this.ext4 = ext4;
}
public String getExt5() {
return ext5;
}
public void setExt5(String ext5) {
this.ext5 = ext5;
}
public List<Map<String, Object>> getDockingUserInfo() {
return dockingUserInfo;
}
@ -325,11 +357,11 @@ public class Achievements extends BaseEntity {
this.isExpertAudit = isExpertAudit;
}
public Long getAttachmentCount() {
public int getAttachmentCount() {
return attachmentCount;
}
public void setAttachmentCount(Long attachmentCount) {
public void setAttachmentCount(int attachmentCount) {
this.attachmentCount = attachmentCount;
}
@ -551,4 +583,27 @@ public class Achievements extends BaseEntity {
return attachments;
}
public String getStorageType() {
return storageType;
}
public void setStorageType(String storageType) {
this.storageType = storageType;
}
public String getMaturityLevel() {
return maturityLevel;
}
public void setMaturityLevel(String maturityLevel) {
this.maturityLevel = maturityLevel;
}
public String getRecommendScope() {
return recommendScope;
}
public void setRecommendScope(String recommendScope) {
this.recommendScope = recommendScope;
}
}

View File

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

View File

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

View File

@ -5,9 +5,12 @@ import java.util.Map;
import com.microservices.dms.achievementLibrary.domain.*;
import com.microservices.dms.behaviorImage.domain.AchievementBehaviorSumVo;
import com.microservices.dms.achievementLibrary.domain.AchievementSourceStatisticVo;
import com.microservices.dms.common.vo.NameCountVo;
import com.microservices.dms.resourceLibrary.domain.CompetitionResourceLibrary;
import com.microservices.dms.resourceLibrary.domain.TaskResourceLibrary;
import com.microservices.dms.resourceLibrary.domain.vo.KeyValVo;
import org.springframework.web.multipart.MultipartFile;
/**
* 成果Service接口
@ -22,15 +25,15 @@ public interface IAchievementsService {
* @param id 成果主键
* @return 成果
*/
public Achievements selectAchievementsById(Long id);
public Achievements selectAchievementsById(Long id, Boolean isOpenView);
/**
* 查询成果列表
*
* @param achievements 成果
* @param achQueryVo 成果
* @return 成果集合
*/
public List<Achievements> selectAchievementsList(Achievements achievements);
public List<Achievements> selectAchievementsList(AchQueryVo achQueryVo);
/**
* 新增成果
@ -106,6 +109,8 @@ public interface IAchievementsService {
int getSearchResult(String achName, Long userId);
AchievementSourceStatisticVo getSource5Statistic(String name);
List<KeyValueVo> getProjectAreasByName(String areaName);
List<String> getExpertAreasByName(String areaName);
@ -123,5 +128,13 @@ public interface IAchievementsService {
List<KeyValVo<String, Long>> get7DayAddExport(AchQueryVo achQueryVo);
List<KeyValueVo> getAllAreas(String areaName);
List<KeyValueVo> getAllField2();
Map<String, Object> batchInsertAchievements(List<Achievements> list, boolean updateSupport, String operName);
String batchImportAchievementsZip(MultipartFile file, boolean updateSupport);
List<AchievementTypeDictVo> achievementsTypeStatistic();
List<AchievementSourceDictVo> achievementsSourceStatistic();
}

View File

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

View File

@ -1,40 +1,57 @@
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.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
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.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.dms.achievementLibrary.domain.*;
import com.microservices.dms.achievementLibrary.mapper.SchoolEnterpriseAchievementsMapper;
import com.microservices.dms.behaviorImage.domain.AchievementBehaviorSumVo;
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.resourceLibrary.domain.Clicker;
import com.microservices.dms.resourceLibrary.domain.CompetitionResourceLibrary;
import com.microservices.dms.resourceLibrary.domain.Searcher;
import com.microservices.dms.resourceLibrary.domain.TaskResourceLibrary;
import com.microservices.dms.common.vo.AchievementSysFileVo;
import com.microservices.dms.common.vo.NameCountVo;
import com.microservices.dms.resourceLibrary.domain.*;
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.ProjectResourceLibraryMapper;
import com.microservices.dms.resourceLibrary.mapper.TaskResourceLibraryMapper;
import com.microservices.dms.utils.CustomMultipartFile;
import com.microservices.dms.utils.DateUtil;
import com.microservices.dms.utils.FileMultipartFile;
import com.microservices.system.api.RemoteDictDataService;
import com.microservices.system.api.RemoteFileService;
import com.microservices.system.api.domain.SysDictData;
import com.microservices.system.api.domain.SysFile;
import com.microservices.system.api.domain.SysFileInfo;
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.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import com.microservices.dms.achievementLibrary.mapper.AchievementsMapper;
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;
/**
@ -46,6 +63,9 @@ import static com.microservices.dms.utils.UrlUtil.getUrlPath;
@Service
@Slf4j
public class AchievementsServiceImpl implements IAchievementsService {
private static final Logger logger = LoggerFactory.getLogger(AchievementsServiceImpl.class);
@Autowired
private AchievementsMapper achievementsMapper;
@ -59,6 +79,12 @@ public class AchievementsServiceImpl implements IAchievementsService {
@Autowired
private IBehaviorImageService behaviorImageService;
@Autowired
private RemoteFileService remoteFileService;
@Autowired
private RemoteDictDataService remoteDictDataService;
@Value("${makerSpaceUrl}")
public String makerSpaceUrl;
@ -75,7 +101,7 @@ public class AchievementsServiceImpl implements IAchievementsService {
* @return 成果
*/
@Override
public Achievements selectAchievementsById(Long id) {
public Achievements selectAchievementsById(Long id, Boolean isOpenView) {
Achievements a = achievementsMapper.selectAchievementsById(id);
if (StringUtils.isNotNull(a)) {
String gender = a.getGender();
@ -86,16 +112,20 @@ public class AchievementsServiceImpl implements IAchievementsService {
img = "images/avatars/User/boy.jpg";
}
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());
if (StringUtils.isNotNull(field1Name)) {
a.setField1Name(field1Name);
}
}
a.setUserImg(img);
a.setImages(buildFileInfoByIdents(a.getImages(), a.getSource()));
a.setAttachments(buildFileInfoByIdents(a.getAttachments(), a.getSource()));
a.setImages( a.getImages() == null ? null : a.getImages().replace("/api/file/common/downloadByIdentifier/", "/api/file/open/downloadByIdentifier/"));
if (isOpenView){
a.setAttachments(buildFileInfoByIdents(a.getAttachments(), a.getSource()));
}
//拼接对接的用户信息
List<Map<String,Object>> dockingUserInfo = achievementsMapper.getDockingUserInfoList(a.getId());
a.setDockingUserInfo(dockingUserInfo);
}
return a;
}
@ -103,36 +133,12 @@ public class AchievementsServiceImpl implements IAchievementsService {
/**
* 查询成果列表
*
* @param achievements 成果
* @param achQueryVo 成果
* @return 成果
*/
@Override
public List<Achievements> selectAchievementsList(Achievements achievements) {
List<Achievements> list = achievementsMapper.selectAchievementsList(achievements);
for (Achievements a : list) {
String gender = a.getGender();
String img = "";
if (StringUtils.isNotEmpty(gender) && gender.equals("1")) {
img = "images/avatars/User/girl.jpg";
} else if (StringUtils.isNotEmpty(gender) && gender.equals("0")) {
img = "images/avatars/User/boy.jpg";
}
a.setUserImg(img);
//根据id获取领域名称
if (StringUtils.isNotEmpty(a.getField1())) {
String field1Name = achievementsMapper.getField1NameByParam(a.getSource(), a.getId());
if (StringUtils.isNotNull(field1Name)) {
a.setField1Name(field1Name);
}
}
a.setImages(buildFileInfoByIdents(a.getImages(), a.getSource()));
a.setAttachments(buildFileInfoByIdents(a.getAttachments(), a.getSource()));
//拼接对接的用户信息
List<Map<String,Object>> dockingUserInfo = achievementsMapper.getDockingUserInfoList(a.getId());
a.setDockingUserInfo(dockingUserInfo);
}
return list;
public List<Achievements> selectAchievementsList(AchQueryVo achQueryVo) {
return getAllResult(achQueryVo);
}
@ -196,7 +202,7 @@ public class AchievementsServiceImpl implements IAchievementsService {
Achievements achievements = achievementsMapper.selectAchievementsById(id);
if(achievements != null){
String source = achievements.getSource();
if(StringUtils.isNotEmpty(source)) {
if(StringUtils.isNotEmpty(source) && !source.equals("5")) {
achievementsMapper.updateRelationAchievements(source,achievements.getSourceId());
}
}
@ -211,20 +217,10 @@ public class AchievementsServiceImpl implements IAchievementsService {
List<Map<String, String>> list = JSON.parseObject(aStr, new TypeReference<List<Map<String, String>>>() {
});
if (Objects.equals(source, "1") || Objects.equals(source, "3")) {
concatUrl(list, gitLinkUrl);
}
if (Objects.equals(source, "2")) {
concatUrl(list, makerSpaceUrl);
}
if (Objects.equals(source, "4")) {
concatUrl(list, gatewayUrl);
}
return JSON.toJSONString(list);
List<Map<String, String>> filterList = list.stream()
.filter(item -> !item.getOrDefault("name","").contains("入驻承诺书"))
.collect(Collectors.toList());
return JSON.toJSONString(filterList);
}
private void concatUrl(List<Map<String, String>> list, String url) {
@ -254,26 +250,28 @@ public class AchievementsServiceImpl implements IAchievementsService {
@Override
public List<Achievements> getAllResult(AchQueryVo achQueryVo) {
//根据查询条件获取所有的成果数据
List<Achievements> list = achievementsMapper.selectAchievementsByParam(achQueryVo);
logger.info("getAllResult USERID======={},user={}", SecurityUtils.getGitlinkUserId(),SecurityUtils.getUsername());
if (achQueryVo.getUserSelf() && SecurityUtils.getGitlinkUserId() != null) {
achQueryVo.setUserId(SecurityUtils.getGitlinkUserId().toString());
achQueryVo.setAllStatus(true);
}
List<Achievements> list = achievementsMapper.selectAchievementsByParamNew(achQueryVo);
//遍历成果数据获取对应成果的点击数
for (Achievements a : list) {
String gender = a.getGender();
String img = "";
String img = "images/avatars/User/boy.jpg";
if (StringUtils.isNotEmpty(gender) && gender.equals("1")) {
img = "images/avatars/User/girl.jpg";
} else if (StringUtils.isNotEmpty(gender) && gender.equals("0")) {
img = "images/avatars/User/boy.jpg";
}
a.setUserImg(img);
//根据id获取领域名称
if (StringUtils.isEmpty(achQueryVo.getSource()) && StringUtils.isNotEmpty(a.getField1())) {
String field1Name = achievementsMapper.getField1NameByParam(a.getSource(), a.getId());
if (StringUtils.isNotNull(field1Name)) {
a.setField1Name(field1Name);
}
}
Long clickSum = achievementsMapper.getAchievementClickSum(a.getId());
a.setClickSum(clickSum);
a.setImages( a.getImages() == null ? null : a.getImages().replace("/api/file/common/downloadByIdentifier/", "/api/file/open/downloadByIdentifier/"));
//拼接对接的用户信息
List<Map<String,Object>> dockingUserInfo = achievementsMapper.getDockingUserInfoList(a.getId());
a.setDockingUserInfo(dockingUserInfo);
}
return list;
}
@ -513,17 +511,19 @@ public class AchievementsServiceImpl implements IAchievementsService {
@Override
public List<AchRelatedResult> getRelatedAchCompetition(Long id, Long sourceId) {
List<AchRelatedResult> relatedResultList = new ArrayList<>();
//想根据开源项目id获取对应的年度信息
List<Long> allSourceId = achievementsMapper.getAllSourceIdCompetition(id, sourceId);
List<String> allYear = achievementsMapper.getDistinctYear(id, allSourceId);
if (StringUtils.isNotNull(allYear)) {
//遍历年度信息获取年度成果数据
for (String paramYear : allYear) {
AchRelatedResult relatedResult = new AchRelatedResult();
relatedResult.setYear(paramYear);
List<AchRelatedVo> tmpList = achievementsMapper.getRelatedAch(id, allSourceId, paramYear);
relatedResult.setAchRelatedVoList(tmpList);
relatedResultList.add(relatedResult);
if (sourceId !=null){
//想根据开源项目id获取对应的年度信息
List<Long> allSourceId = achievementsMapper.getAllSourceIdCompetition(id, sourceId);
List<String> allYear = achievementsMapper.getDistinctYear(id, allSourceId);
if (StringUtils.isNotNull(allYear)) {
//遍历年度信息获取年度成果数据
for (String paramYear : allYear) {
AchRelatedResult relatedResult = new AchRelatedResult();
relatedResult.setYear(paramYear);
List<AchRelatedVo> tmpList = achievementsMapper.getRelatedAch(id, allSourceId, paramYear);
relatedResult.setAchRelatedVoList(tmpList);
relatedResultList.add(relatedResult);
}
}
}
return relatedResultList;
@ -532,17 +532,19 @@ public class AchievementsServiceImpl implements IAchievementsService {
@Override
public List<AchRelatedResult> getRelatedAchTask(Long id, Long sourceId) {
List<AchRelatedResult> relatedResultList = new ArrayList<>();
//想根据开源项目id获取对应的年度信息
List<Long> allSourceId = achievementsMapper.getAllSourceIdTask(id, sourceId);
List<String> allYear = achievementsMapper.getDistinctYear(id, allSourceId);
if (StringUtils.isNotNull(allYear)) {
//遍历年度信息获取年度成果数据
for (String paramYear : allYear) {
AchRelatedResult relatedResult = new AchRelatedResult();
relatedResult.setYear(paramYear);
List<AchRelatedVo> tmpList = achievementsMapper.getRelatedAch(id, allSourceId, paramYear);
relatedResult.setAchRelatedVoList(tmpList);
relatedResultList.add(relatedResult);
if (sourceId !=null){
//想根据开源项目id获取对应的年度信息
List<Long> allSourceId = achievementsMapper.getAllSourceIdTask(id, sourceId);
List<String> allYear = achievementsMapper.getDistinctYear(id, allSourceId);
if (StringUtils.isNotNull(allYear)) {
//遍历年度信息获取年度成果数据
for (String paramYear : allYear) {
AchRelatedResult relatedResult = new AchRelatedResult();
relatedResult.setYear(paramYear);
List<AchRelatedVo> tmpList = achievementsMapper.getRelatedAch(id, allSourceId, paramYear);
relatedResult.setAchRelatedVoList(tmpList);
relatedResultList.add(relatedResult);
}
}
}
return relatedResultList;
@ -587,6 +589,15 @@ public class AchievementsServiceImpl implements IAchievementsService {
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
public List<KeyValueVo> getProjectAreasByName(String areaName) {
return achievementsMapper.getProjectAreasByName(areaName);
@ -629,4 +640,409 @@ public class AchievementsServiceImpl implements IAchievementsService {
return resultVal;
}
@Override
public List<KeyValueVo> getAllField2() {
List<KeyValueVo> resultVal = achievementsMapper.getAllField2();
return resultVal;
}
@Override
public List<AchievementTypeDictVo> achievementsTypeStatistic() {
List<NameCountVo> nameCountVos = achievementsMapper.groupByAchievementsType();
R<List<SysDictData>> feignResult = remoteDictDataService.dictType("dms_achievement_type");
return Optional.ofNullable(feignResult.getData()).orElse(Collections.emptyList())
.stream().map(sysDictData -> {
AchievementTypeDictVo dictVo = new AchievementTypeDictVo();
BeanUtils.copyProperties(sysDictData, dictVo);
dictVo.setCnt(nameCountVos.isEmpty() ? 0 : nameCountVos.stream().filter(dict -> sysDictData.getDictValue().equals(dict.getName()))
.mapToLong(NameCountVo::getCnt).sum());
return dictVo;
}).collect(Collectors.toList());
}
@Override
public List<AchievementSourceDictVo> achievementsSourceStatistic() {
List<NameCountVo> nameCountVos = achievementsMapper.groupByAchievementsSource();
R<List<SysDictData>> feignResult = remoteDictDataService.dictType("dms_achievement_source");
return Optional.ofNullable(feignResult.getData()).orElse(Collections.emptyList())
.stream().map(sysDictData -> {
AchievementSourceDictVo dictVo = new AchievementSourceDictVo();
BeanUtils.copyProperties(sysDictData, dictVo);
dictVo.setCnt(nameCountVos.isEmpty() ? 0 : nameCountVos.stream().filter(dict -> dict.getName().equals(sysDictData.getDictValue()))
.mapToLong(NameCountVo::getCnt).sum());
return dictVo;
}).collect(Collectors.toList());
}
@Override
public Map<String, Object> 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();
List<Achievements> successList = new ArrayList<>();
for (Achievements achievements : list) {
try {
// 查重根据项目名称查询
Achievements exist = achievementsMapper.selectAchievementsByAchievementName(achievements.getAchievementName());
Long gitlinkUserId=SecurityUtils.getGitlinkUserId();
Boolean gitlinkUserAdmin=achievementsMapper.isGitlinkAdminUser(gitlinkUserId) == 1;
achievements.setStatus(gitlinkUserAdmin ? "1" : "0");
achievements.setOwnerId(gitlinkUserId.toString());
if (null == exist) {
achievements.setCreateBy(operName);
achievements.setCreateTime(new Date());
achievements.setUpdateBy(operName);
achievements.setUpdateTime(new Date());
achievementsMapper.insertAchievements(achievements);
successNum++;
successList.add(achievements);
successMsg.append("<br/>").append(successNum).append("、成果 ").append(achievements.getAchievementName()).append(" 导入成功");
} else {
if (updateSupport && exist.getOwnerId() != null && exist.getOwnerId().equals(gitlinkUserId.toString())) {
achievements.setId(exist.getId());
achievements.setCreateBy(exist.getCreateBy());
achievements.setCreateTime(exist.getCreateTime());
achievements.setUpdateBy(operName);
achievements.setUpdateTime(new Date());
achievementsMapper.updateAchievements(achievements);
successNum++;
successList.add(achievements);
successMsg.append("<br/>").append(successNum).append("、成果 ").append(achievements.getAchievementName()).append(" 更新成功");
} else if (!updateSupport && exist.getOwnerId() != null && exist.getOwnerId().equals(gitlinkUserId.toString())) {
failureNum++;
failureMsg.append("<br/>").append(failureNum).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);
finalMsg.append("<br/>***压缩包共读取到 ").append(successList.stream().mapToInt(Achievements::getAttachmentCount).sum()).append(" 个文件");
}
if (failureNum > 0) {
finalMsg.append("<br/>共 ").append(failureNum).append(" 条数据导入失败:").append(failureMsg);
}
Map<String, Object> result = new HashMap<>();
result.put("successNum", successNum);
result.put("failureNum", failureNum);
result.put("message", finalMsg);
return result;
}
@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<>();
Map<String, AchievementSysFileVo> coveImages = 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.endsWith("附件") && (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.endsWith("成果图片视频") && 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.endsWith("应用图片视频") && 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.setName(videoFile.getOriginalFilename());
transferVo.setK(videoFile.getOriginalFilename());
transferVo.setV(sysFile.getUrl());
mp4ImagesExt2.put(fileName, transferVo);
}
// 清理临时文件
tempFile.delete();
}
// 3. 处理图片文件
else if (parentPath.endsWith("成果图片视频") && 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-open", "import", SecurityConstants.INNER);
if (R.SUCCESS == fileResult.getCode()) {
SysFile sysFile = fileResult.getData();
AchievementSysFileVo transferVo = new AchievementSysFileVo();
BeanUtils.copyProperties(sysFile, transferVo);
transferVo.setName(imageFile.getOriginalFilename());
transferVo.setK(imageFile.getOriginalFilename());
transferVo.setV(sysFile.getUrl());
mp4ImagesExt1.put(fileName, transferVo);
}
}
// 3. 处理图片文件
else if (parentPath.endsWith("应用图片视频") && 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-open", "import", SecurityConstants.INNER);
if (R.SUCCESS == fileResult.getCode()) {
SysFile sysFile = fileResult.getData();
AchievementSysFileVo transferVo = new AchievementSysFileVo();
BeanUtils.copyProperties(sysFile, transferVo);
transferVo.setName(imageFile.getOriginalFilename());
transferVo.setK(imageFile.getOriginalFilename());
transferVo.setV(sysFile.getUrl());
mp4ImagesExt2.put(fileName, transferVo);
}
}
else if (parentPath.endsWith("附件")) {
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-open", "import", SecurityConstants.INNER);
if (R.SUCCESS == fileResult.getCode()) {
SysFile sysFile = fileResult.getData();
AchievementSysFileVo transferVo = new AchievementSysFileVo();
BeanUtils.copyProperties(sysFile, transferVo);
transferVo.setName(attachmentFile.getOriginalFilename());
transferVo.setK(attachmentFile.getOriginalFilename());
transferVo.setV(sysFile.getUrl());
attachmentMap.put(fileName, transferVo);
}
// 清理临时文件
tempFile.delete();
}// 3. 处理图片文件
else if (parentPath.endsWith("封面图") && 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-open", "import", SecurityConstants.INNER);
if (R.SUCCESS == fileResult.getCode()) {
SysFile sysFile = fileResult.getData();
AchievementSysFileVo transferVo = new AchievementSysFileVo();
BeanUtils.copyProperties(sysFile, transferVo);
transferVo.setName(imageFile.getOriginalFilename());
transferVo.setK(imageFile.getOriginalFilename());
transferVo.setV(sysFile.getUrl());
coveImages.put(fileName, transferVo);
}
}
zipInputStream.closeEntry();
}
logger.info("✅ 使用 " + charset.displayName() + " 编码成功读取 ZIP");
List<Achievements> saveDataList = excelDataList.stream()
.filter(item -> StringUtils.isNotEmpty(item.getSource()) && StringUtils.isNotEmpty(item.getAchievementName()))
.collect(Collectors.toList());
saveDataList.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("入驻承诺书") || item.getKey().contains(achievements.getAchievementName()))
.map(Map.Entry::getValue)
.collect(Collectors.toList());
List<Object> coveImagesList = coveImages.entrySet().stream()
.filter(item -> item.getKey().contains(achievements.getAchievementName()))
.map(Map.Entry::getValue)
.collect(Collectors.toList());
if (!mp4ImagesExt1List.isEmpty()){
achievements.setExt1(JSONObject.toJSONString(mp4ImagesExt1List));
achievements.setAttachmentCount(achievements.getAttachmentCount() + mp4ImagesExt1List.size());
}
if (!mp4ImagesExt2List.isEmpty()){
achievements.setExt2(JSONObject.toJSONString(mp4ImagesExt2List));
achievements.setAttachmentCount(achievements.getAttachmentCount() + mp4ImagesExt2List.size());
}
if (!filterAttachmentList.isEmpty()){
achievements.setAttachments(JSONObject.toJSONString(filterAttachmentList));
achievements.setAttachmentCount(achievements.getAttachmentCount() + filterAttachmentList.size());
}
if (!coveImagesList.isEmpty()){
achievements.setImages(JSONObject.toJSONString(coveImagesList));
achievements.setAttachmentCount(achievements.getAttachmentCount() + coveImagesList.size());
}
});
// 保存 Excel 数据到数据库
String operName = SecurityUtils.getUsername();
Map<String, Object> resultMap = saveDataList.isEmpty() ? new HashMap<>() : batchInsertAchievements(saveDataList, updateSupport, operName);
//
return resultMap.get("message").toString();
} 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;
import java.io.*;
import java.util.*;
import java.util.stream.Collectors;
import com.alibaba.fastjson2.JSONObject;
import com.microservices.common.core.constant.SecurityConstants;
import com.microservices.common.core.constant.UserConstants;
import com.microservices.common.core.domain.R;
import com.microservices.common.core.exception.ServiceException;
import com.microservices.common.core.utils.DateUtils;
import com.microservices.common.core.utils.StringUtils;
import com.microservices.common.datasource.annotation.Slave;
import com.microservices.common.security.utils.SecurityUtils;
import com.microservices.dms.achievementLibrary.domain.AchievementTeam;
import com.microservices.dms.achievementLibrary.domain.Achievements;
import com.microservices.dms.achievementLibrary.mapper.AchievementTeamMapper;
import com.microservices.dms.achievementLibrary.mapper.AchievementsMapper;
import com.microservices.dms.achievementLibrary.service.IAchievementsService;
import com.microservices.system.api.domain.SysUser;
import com.microservices.system.api.RemoteFileService;
import com.microservices.system.api.model.LoginUser;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.microservices.dms.achievementLibrary.mapper.SchoolEnterpriseAchievementsMapper;
import com.microservices.dms.achievementLibrary.domain.SchoolEnterpriseAchievements;
import com.microservices.dms.achievementLibrary.service.ISchoolEnterpriseAchievementsService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;
/**
* 校企成果Service业务层处理
@ -41,6 +50,9 @@ public class SchoolEnterpriseAchievementsServiceImpl implements ISchoolEnterpris
@Autowired
private AchievementsMapper achievementsMapper;
@Autowired
private RemoteFileService remoteFileService;
/**
* 查询校企成果
*
@ -124,6 +136,144 @@ public class SchoolEnterpriseAchievementsServiceImpl implements ISchoolEnterpris
return UserConstants.UNIQUE;
}
/**
* 导入成果数据包含图片处理
*/
@Override
@Transactional
@Slave
public String batchInsertSchoolEnterpriseAchievements(List<SchoolEnterpriseAchievements> list,
Map<String, MultipartFile> imageMap,
boolean updateSupport,
String operName) {
if (list == null || list.isEmpty()) {
throw new RuntimeException("导入成果数据不能为空!");
}
int successNum = 0;
int failureNum = 0;
int imageErrorNum = 0; // 新增图片处理失败数
StringBuilder successMsg = new StringBuilder();
StringBuilder failureMsg = new StringBuilder();
StringBuilder imageErrorMsg = new StringBuilder(); // 新增图片错误信息
for (SchoolEnterpriseAchievements schoolEnterpriseAchievement : list) {
try {
// 处理图片字段优化后返回处理结果不抛出异常
String imageHandleResult = handleAchievementImage(schoolEnterpriseAchievement, imageMap);
if (imageHandleResult.contains("失败") || imageHandleResult.contains("异常")) {
imageErrorNum++;
imageErrorMsg.append("<br/>").append(imageErrorNum).append("、成果 ").append(schoolEnterpriseAchievement.getAchievementName()).append(" 图片处理失败:").append(imageHandleResult);
}
// 原有查重和入库逻辑不受图片处理影响
SchoolEnterpriseAchievements exist = schoolEnterpriseAchievementsMapper.selectSchoolEnterpriseAchievementsByName(schoolEnterpriseAchievement.getAchievementName());
if (exist == null) {
schoolEnterpriseAchievement.setCreateBy(operName);
schoolEnterpriseAchievement.setCreateTime(new Date());
schoolEnterpriseAchievement.setUpdateBy(operName);
schoolEnterpriseAchievement.setUpdateTime(new Date());
schoolEnterpriseAchievement.setStatus("2");
schoolEnterpriseAchievementsMapper.insertSchoolEnterpriseAchievements(schoolEnterpriseAchievement);
successNum++;
successMsg.append("<br/>").append(successNum).append("、成果 ").append(schoolEnterpriseAchievement.getAchievementName()).append(" 导入成功");
} else {
if (updateSupport) {
schoolEnterpriseAchievement.setId(exist.getId());
schoolEnterpriseAchievement.setCreateBy(exist.getCreateBy());
schoolEnterpriseAchievement.setCreateTime(exist.getCreateTime());
schoolEnterpriseAchievement.setUpdateBy(operName);
schoolEnterpriseAchievement.setUpdateTime(new Date());
schoolEnterpriseAchievementsMapper.updateSchoolEnterpriseAchievements(schoolEnterpriseAchievement);
successNum++;
successMsg.append("<br/>").append(successNum).append("、成果 ").append(schoolEnterpriseAchievement.getAchievementName()).append(" 更新成功");
} else {
failureNum++;
failureMsg.append("<br/>").append(failureNum).append("、成果 ").append(schoolEnterpriseAchievement.getAchievementName()).append(" 已存在(未开启更新)");
}
}
} catch (Exception e) {
failureNum++;
String msg = "<br/>" + failureNum + "、成果 " + schoolEnterpriseAchievement.getAchievementName() + " 导入失败:";
failureMsg.append(msg).append(e.getMessage());
}
}
// 组装最终提示信息包含图片处理错误
StringBuilder finalMsg = new StringBuilder();
if (successNum > 0) {
finalMsg.append("").append(successNum).append(" 条数据导入/更新成功:").append(successMsg);
}
if (failureNum > 0) {
finalMsg.append("<br/>共 ").append(failureNum).append(" 条数据导入失败:").append(failureMsg);
}
if (imageErrorNum > 0) {
finalMsg.append("<br/>共 ").append(imageErrorNum).append(" 条数据图片处理失败:").append(imageErrorMsg);
}
return finalMsg.toString();
}
/**
* 处理成果图片上传并格式化存储FastJSON解析版
* @param achievement 成果实体
* @param imageMap Excel解析出的图片映射表
* @return 图片处理结果提示成功/失败信息
*/
private String handleAchievementImage(SchoolEnterpriseAchievements achievement, Map<String, MultipartFile> imageMap) {
String imageCellKey = achievement.getImages();
if (StringUtils.isEmpty(imageCellKey) || !imageMap.containsKey(imageCellKey)) {
achievement.setImages("[]");
return "无图片,跳过处理";
}
try {
MultipartFile file = imageMap.get(imageCellKey);
byte[] fileBytes = file.getBytes();
if (fileBytes == null || fileBytes.length == 0) {
return "图片文件为空,跳过处理";
}
// 构建 HashMap 参数
String originalFilename = StringUtils.isEmpty(file.getOriginalFilename())
? "excel_import_image_" + System.currentTimeMillis() + ".png"
: file.getOriginalFilename();
Map<String, Object> params = new HashMap<>();
params.put("base64Data", Base64.getEncoder().encodeToString(fileBytes));
params.put("fileName", originalFilename);
params.put("contentType", file.getContentType());
params.put("size", fileBytes.length);
// 调用 Base64 上传接口
R<?> fileResult = remoteFileService.uploadBase64(params, "dms", "import", SecurityConstants.INNER);
// 解析结果与原逻辑相同
if (StringUtils.isNull(fileResult) || StringUtils.isNull(fileResult.getData())) {
String errorMsg = !StringUtils.isNull(fileResult) ? fileResult.getMsg() : "接口返回异常";
return "图片上传失败:" + errorMsg;
}
JSONObject resultJson = JSONObject.parseObject(JSONObject.toJSONString(fileResult.getData()));
String fileOriginName = resultJson.getString("fileOriginName");
String url = resultJson.getString("downloadUrl");
Integer fileId = resultJson.getInteger("fileId");
if (StringUtils.isEmpty(fileOriginName) || StringUtils.isEmpty(url) || fileId == null) {
return "图片上传成功,但返回数据不完整";
}
String imageJson = String.format("[{\"k\":\"%s\",\"v\":\"%s\",\"id\":%d}]",
fileOriginName, url, fileId);
achievement.setImages(imageJson);
return "图片处理成功";
} catch (Exception e) {
return "图片处理异常:" + e.getMessage();
}
}
/**
* 修改校企成果

View File

@ -3,6 +3,8 @@ package com.microservices.dms.common.controller;
import com.microservices.common.core.web.controller.BaseController;
import com.microservices.common.core.web.domain.AjaxResult;
import com.microservices.common.core.web.page.TableDataInfo;
import com.microservices.common.security.annotation.RequiresLogin;
import com.microservices.common.security.utils.SecurityUtils;
import com.microservices.dms.achievementLibrary.domain.AchQueryVo;
import com.microservices.dms.achievementLibrary.service.BlockChainService;
import com.microservices.dms.achievementLibrary.service.IAchievementsService;
@ -107,6 +109,44 @@ public class OpenController extends BaseController {
return getDataTable(achievementsService.getAllResult(achQueryVo));
}
/**
* 获取成果详细信息
*/
@GetMapping(value = "/achievements/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id) {
return success(achievementsService.selectAchievementsById(id, true));
}
@ApiOperation("首页成果分类统计")
@GetMapping("/achievements/typeStatistic")
public AjaxResult achievementsTypeStatistic() {
return success(achievementsService.achievementsTypeStatistic());
}
@ApiOperation("首页成果来源统计")
@GetMapping("/achievements/sourceStatistic")
public AjaxResult achievementsSourceStatistic() {
return success(achievementsService.achievementsSourceStatistic());
}
@ApiOperation("根据成果ID,获取成果的相关行为数据统计(点击量、搜索量、附件下载量、收藏、关注)")
@GetMapping("/achievements/getActDataStatisticById")
public AjaxResult getActDataStatisticById(Long id) {
return success(achievementsService.getActDataStatisticById(id));
}
@ApiOperation(value = "获取开源竞赛的相关成果")
@GetMapping("/achievements/getRelatedAchCompetition")
public AjaxResult getRelatedAchCompetition(Long id, Long sourceId) {
return success(achievementsService.getRelatedAchCompetition(id, sourceId));
}
@ApiOperation("根据当前用户以及成果ID,获取当前成果的 是否收藏与关注状态")
@GetMapping("/achievements/getWatchFavoriteStatusById")
public AjaxResult getWatchFavoriteStatusById(Long id, Long userId) {
return success(achievementsService.getWatchFavoriteStatusById(id, userId));
}
@ApiOperation("近七日用户行为数据")
@GetMapping("/achievements/getUerActionData")
public AjaxResult getUerActionData() {

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

@ -0,0 +1,9 @@
package com.microservices.dms.common.vo;
import lombok.Data;
@Data
public class NameCountVo {
private String name;
private long cnt;
}

View File

@ -1,11 +1,15 @@
package com.microservices.dms.resourceLibrary.controller;
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.web.controller.BaseController;
import com.microservices.common.core.web.domain.AjaxResult;
import com.microservices.common.core.web.page.GenericsTableDataInfo;
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.vo.CompetitionListVo;
import com.microservices.dms.resourceLibrary.service.CompetitionResourceLibraryService;
@ -17,6 +21,7 @@ import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
@ -78,6 +83,17 @@ public class CompetitionResourceLibraryController extends BaseController {
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}")
public AjaxResult getInfo(@PathVariable("id") Long id) {
return success(competitionResourceLibraryService.selectCompetitionResourceLibraryById(id));

View File

@ -1,6 +1,7 @@
package com.microservices.dms.resourceLibrary.controller;
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.web.controller.BaseController;
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.dms.resourceLibrary.domain.ProjectResourceLibrary;
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.service.ProjectResourceLibraryService;
import com.microservices.dms.resourceLibrary.service.TaskResourceLibraryService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
@ -122,6 +125,23 @@ public class ProjectResourceLibraryController extends BaseController {
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}")
public AjaxResult getInfo(@PathVariable("id") Long id) {
return success(projectResourceLibraryService.selectProjectResourceLibraryById(id));

View File

@ -1,6 +1,7 @@
package com.microservices.dms.resourceLibrary.controller;
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.web.controller.BaseController;
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.log.annotation.Log;
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.vo.TaskListVo;
import com.microservices.dms.resourceLibrary.domain.vo.UserActionDataVo;
@ -18,6 +20,7 @@ import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
@ -134,6 +137,17 @@ public class TaskResourceLibraryController extends BaseController {
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}")
public AjaxResult getInfo(@PathVariable("id") Long id) {
return success(taskResourceLibraryService.selectTaskResourceLibraryById(id));

View File

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

View File

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

View File

@ -21,21 +21,28 @@ public class TaskResourceLibrary extends BaseEntity {
//
private Long id;
//创客任务ID
@Excel(name = "任务ID")
private Long taskId;
//
private Long paperId;
//创客任务名称
@Excel(name = "任务名称")
private String taskName;
//任务领域
@Excel(name = "成果领域")
private String taskDomain;
//创客任务金额
@Excel(name = "任务金额(元)")
private BigDecimal taskAmount;
//胜出交稿名称
@Excel(name = "成果编号")
private String submissionName;
//交稿时间
@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;
//交稿人
@Excel(name = "交稿人")
private String submitterName;
//交稿类型
private String submissionType;
@ -50,9 +57,11 @@ public class TaskResourceLibrary extends BaseEntity {
//联系电话
private String contactPhone;
//是否已转入成果库
@Excel(name = "转入状态", readConverterExp = "0=未转入,1=已转入")
private Long isTransferredToResultsLibrary;
//转入成果库时间
@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;
//是否精选成果 0否 1是
private Long isSelectedResult;

View File

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

View File

@ -73,4 +73,6 @@ public interface ProjectResourceLibraryMapper {
String getUserIdBYLogin(@Param("userId")String userId);
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);
double getConvertedTaskAmount2();
TaskResourceLibrary selectTaskResourceLibraryByTaskName(@Param("taskName") String taskName);
}

View File

@ -149,4 +149,61 @@ public class CompetitionResourceLibraryService {
// 这里替换成你的实际逻辑
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() {
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.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
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) {
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);
}
}
}

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