diff --git a/.gitignore b/.gitignore index c2f12895..eb80c82e 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,4 @@ nbdist/ effective-pom.xml /datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/resources/参考代码/ +/datai-scenes/datai-scene-salesforce/docs/sql/ diff --git a/README.md b/README.md index d56dc933..0484699e 100644 --- a/README.md +++ b/README.md @@ -1,222 +1,277 @@ -

- - logo - - + - - logo - -

-

datai v3.8.9-G

-

基于SpringBoot3+Vue3前后端分离的Java快速开发框架

-

- -

+# Datai - Salesforce 集成系统 -# 当前版本是3.8.9-G +## 项目概述 -本人的其他两个推荐搭配的项目 +Datai 是一个基于若依Geek框架构建的企业级管理系统,采用模块化架构设计,提供可扩展的插件和场景模块。系统集成了 Salesforce 平台,支持多种 API 交互方式,实现数据同步、认证管理和元数据处理等核心功能。 -1. [DataI-App-Geek: 这是集成系统生态的小程序版本 (gitee.com)](https://gitee.com/geek-xd/geek-uniapp-vue3-uview-plus-uchart) -2. [DataI-Vue3-Geek: 这是集成系统生态的Vue3版本 (gitee.com)](https://gitee.com/geek-xd/ruo-yi-vue3-geek) +**当前实现状态**:项目目前专注于从 Salesforce 源系统同步数据到本地数据库,包括元数据管理、批量数据同步、实时数据同步等功能。 -与本项目同为一个作者开发,兼容性最好,学习成本最低。 +## 技术栈 -# 引言 +| 技术 | 版本 | 说明 | +|------|------|------| +| Java | 21 / 22 | 根项目使用 Java 21,Salesforce 模块使用 Java 22 | +| Spring Boot | 3.5.7 | 核心框架 | +| MyBatis | 3.5.16 | 持久层框架 | +| MySQL | 8.3.0 | 关系型数据库 | +| PostgreSQL | 42.7.7 | 关系型数据库 | +| Knife4j | 4.5.0 | API 文档工具 | +| Druid | 1.2.27 | 数据库连接池 | +| Maven | - | 构建工具 | +| Salesforce Partner API | 0.0.1 | Salesforce SOAP API 客户端 | +| Salesforce Pub/Sub Client | 0.2.21 | Salesforce CDC 实时同步客户端 | +| Apache Avro | 1.11.3 | 数据序列化格式 | +| Fastjson | 2.0.58 | JSON 解析器 | +| JWT | 0.12.6 | Token 生成与解析 | +| PageHelper | 2.1.1 | 分页插件 | -DataI-Vue与DataI-App是基于SpringBoot2+Vue2打造的企业级开发框架,得到了广大开发者的喜爱和积极反馈。随着技术的迭代进步,SpringBoot3与Vue3逐渐进入开发者的视野。为了满足开发者对于新技术的追求,DataI官方文档提供了SpringBoot2至SpringBoot3的升级方法。与此同时,社区也涌现出了DataI-Vue3、DataI-App-Vue3的版本,展现了开发者社区对于技术升级的热情与努力。 +## 项目结构 -然而,在升级的过程中,官方的方法为了兼顾Java1.8的特性与一些老旧的方法,未完全拥抱SpringBoot3与Java17的全部特性。而社区的DataI-Vue3、DataI-App-Vue3版本由于出自不同的团队之手,兼容性及整合性上存在些许不足。更为关键的是,尽管这些版本支持TypeScript,但缺乏与之相匹配的tsconfig.json配置文件,这使得在主流编辑器如VSCode中,TypeScript的语法提示环境并未达到最佳状态。 - -鉴于此,datai生态应运而生。它旨在为广大开发者提供一个既保留原版本核心特性,又整合社区版优点的全新解决方案。在datai中,我们深入调研了企业开发中常用的DataI扩展,并直接在框架中集成,确保开发者能够快速上手,高效开发。同时,我们采用了最新的SpringBoot3+Vue3技术栈,彻底移除了为了兼容Java1.8而保留的老旧方法。更为重要的是,我们为TypeScript开发环境加入了常用的tsconfig.json配置,使得开发者在VSCode等编辑器中能够获得更为舒适、便捷的语法提示体验。 - -datai不仅仅是一个简单的升级版本,更是对于DataI生态的一次全面优化与整合。我们相信,通过datai,开发者将能够更为高效、愉悦地开发出优秀的企业级应用。 - -## 平台简介 - -若依是一套全部开源的快速开发平台,毫无保留给个人及企业免费使用。 - -* 前端采用Vue3、Element Plus。 -* 后端采用Spring Boot3、Spring Security、Redis & Jwt。 -* 权限认证使用Jwt,支持多终端认证系统。 -* 支持加载动态权限菜单,多方式轻松权限控制。 -* 高效率开发,使用代码生成器可以一键生成前后端代码。 -* 多数据源与分库分表默认集成 -* 所有非基本模块可随意插拔,让开发更加简单高效 -* 提供了多个工具模块助力开发,如:在线接口模块、mybatis-jpa模块 -* 提供了多个常见业务模块简化开发,如:第三方认证模块、支付模块 -* 提供了多个常见的服务模块集成开发,如:websocket模块、minio模块 -* 特别鸣谢:[element](https://github.com/ElemeFE/element),[vue-element-admin](https://github.com/PanJiaChen/vue-element-admin),[eladmin-web](https://github.com/elunez/eladmin-web)。 -* 阿里云折扣场:[点我进入](http://aly.datai.vip),腾讯云秒杀场:[点我进入](http://txy.datai.vip)   -* 阿里云优惠券:[点我领取](https://www.aliyun.com/minisite/goods?userCode=brki8iof&share_source=copy_link),腾讯云优惠券:[点我领取](https://cloud.tencent.com/redirect.php?redirect=1025&cps_key=198c8df2ed259157187173bc7f4f32fd&from=console)   - -## 本项目与原项目的区别 - -### 核心 - -**模块化架构设计,支持各个模块的快速安拆,对第三方认证、第三方支付模块设计了基础的规范和基础模块。** - -### 细节 - -1. 改用SpringBoot3+java17的更新的技术栈并改掉所有的弃用的方法,全面拥抱java17的全新特性。 -2. 升级了代码生成器(配合本项目的vue3版本才可用),使关联表生成更加简单! -3. 改用最新版本的SpringSecurity安全框架,以及采用最新的lambda 表达式的配置方式,更加通俗易懂! -4. 自动Api文档以springfox替代springdoc来适配knife4j框架的4.x版本,更好的适配springboot3! -5. 默认引入mybatis-plus增强mybatis,并自创工具模块mybatis-jpa简化CRUD! -6. 默认引入lombok简化代码(注:基础模块并未使用mybatis-plus和lombok,对这两个扩展有争议的小伙伴可以直接删除,不会影响到框架本身滴,以及knife4j直接删除也不会影响到springdoc,主要还是为了方便咱们开发者呢!) -7. 提供了大量可随意插拔的模块,助力快速开发!同时datai插件集成中的常用插件也以模块的形式直接集成在了项目中,也是可以随意插拔的嗷~ - -## 模块介绍(简单开发必看) - -* 最简单的开发就是删除所有的可移除模块,按需添加模块。 -* 测试中的模块请自己使用的时候一定要测试一下。 -* 对于小白,开发中的模块请直接删除。 - -```plaintext -com.datai -├── datai-admin // 后台服务 -├── common // 工具类 -│ └── annotation // 自定义注解 -│ └── config // 全局配置 -│ └── constant // 通用常量 -│ └── core // 核心控制 -│ └── enums // 通用枚举 -│ └── exception // 通用异常 -│ └── filter // 过滤器处理 -│ └── utils // 通用类处理 -├── framework // 框架核心 -│ └── aspectj // 注解实现 -│ └── config // 系统配置 -│ └── datasource // 数据权限 -│ └── interceptor // 拦截器 -│ └── manager // 异步处理 -│ └── security // 权限控制 -│ └── web // 前端控制 -├── datai-models // 业务模块 -│ └── online // 在线开发模块(可移除) -│ └── quartz // 定时任务(可移除) -│ └── generator // 代码生成(可移除) -│ └── form // 自定义在线表单(可移除) -│ └── flowable // 流程管理(可移除)(依赖于form模块) -│ └── message // 消息模块(可移除)(可以与phone、email模块协同) -│ └── starter // 业务场景启动器 -├── datai-plugins // 插件 -│ └── ehcache // ehcache缓存插件(与redis模块同类,两者二选一) -│ └── mybatis-jpa // mybatis-jpa插件(可移除)(简化CRUD,以数据模型为基础开发) -│ └── mybatis-plus // mybatis-plus插件(可移除)(简化CRUD) -│ └── mybatis-interceptor // mybatis-interceptor插件(可移除)(简化数据鉴权和分页,扩展性强) -│ └── atomikos // atomikos分布式事务插件(可移除) -│ └── netty // netty插件(可移除) -│ └── rabbitmq // rabbitmq队列服务模块 -│ └── redis // redis缓存服务模块(与ehcache插件同类,两者二选一) -│ └── websocket // websocket插件(可移除) -│ └── starter // 插件整合模块 -├── datai-scenes // 业务场景 -│ ├── datai-scenes-pay // 支付场景(基本业务完成) -│ │ └── common // 支付框架基础模块(基础框架搭建完成) -│ │ └── sqb // 收钱吧支付模块(功能可用,业务需要开发) -│ │ └── wx // 微信支付模块(基础业务完成) -│ │ └── alipay // 支付宝支付模块(基础业务完成) -│ │ └── starter // 支付场景启动器 -│ ├── datai-scenes-auth // 第三方认证场景(基础框架搭建完成) -│ │ └── common // 第三方认证基础模块(开发中) -│ │ └── justauth // 网站第三方认证模块(测试中,参照若依扩展改进,因没有这么多场景的code,请大家测试出问题后help解决一下发出来) -│ │ └── wx // 微信小程序认证模块(测试中) -│ │ └── phone // 手机认证模块(基础业务完成) -│ │ └── email // 邮箱认证模块(基础业务完成) -│ │ └── starter // 第三方认证启动器 -│ ├── datai-scenes-file // 文件服务系统(支持公开访问、临时凭证、分片上传等常见功能) -│ │ └── common // 基础模块和本地磁盘方式 -│ │ └── minio // minio分布式文件服务 -│ │ └── oss-alibaba // alibaba的oss云储存服务 -│ │ └── starter // 第三方认证启动器 -├── datai-system // 系统代码 +``` +datai/ +├── datai-admin/ # 管理后台启动模块 +├── datai-framework/ # 框架核心模块 +├── datai-system/ # 系统管理模块 +├── datai-common/ # 通用工具模块 +├── datai-scenes/ # 场景模块 +│ ├── datai-scene-salesforce/ # Salesforce 集成场景 +│ │ ├── datai-salesforce-auth/ # 认证相关模块 +│ │ ├── datai-salesforce-common/ # 通用工具和常量 +│ │ ├── datai-salesforce-integration/ # 核心集成模块 +│ │ ├── datai-salesforce-setting/ # 配置管理模块 +│ │ └── datai-salesforce-starter/ # 启动器模块 +│ ├── datai-scene-auth/ # 认证场景 +│ │ ├── datai-auth-common/ # 认证通用模块 +│ │ ├── datai-auth-starter/ # 认证启动器 +│ │ ├── datai-oauth-justauth/ # JustAuth OAuth +│ │ ├── datai-oauth-wx/ # 微信 OAuth +│ │ ├── datai-tfa-email/ # 邮箱双因子认证 +│ │ └── datai-tfa-phone/ # 手机双因子认证 +│ ├── datai-scene-pay/ # 支付场景 +│ │ ├── datai-pay-common/ # 支付通用模块 +│ │ ├── datai-pay-starter/ # 支付启动器 +│ │ ├── datai-pay-alipay/ # 支付宝支付 +│ │ ├── datai-pay-wx/ # 微信支付 +│ │ └── datai-pay-sqb/ # 收钱吧支付 +│ └── datai-scene-file/ # 文件场景 +│ ├── datai-file-common/ # 文件通用模块 +│ ├── datai-file-starter/ # 文件启动器 +│ ├── datai-file-local/ # 本地文件存储 +│ ├── datai-file-minio/ # MinIO 文件存储 +│ └── datai-file-aliyun-oss/ # 阿里云 OSS 文件存储 +├── datai-plugins/ # 插件模块 +│ ├── datai-plugins-starter/ # 插件启动器 +│ ├── datai-mybatis-plus/ # MyBatis Plus 插件 +│ ├── datai-cache-redis/ # Redis 缓存插件 +│ ├── datai-cache-ehcache/ # Ehcache 缓存插件 +│ ├── datai-rabbitmq/ # RabbitMQ 消息队列插件 +│ ├── datai-websocket/ # WebSocket 插件 +│ ├── datai-netty/ # Netty 网络插件 +│ └── datai-atomikos/ # Atomikos 分布式事务插件 +└── datai-models/ # 模型模块 + ├── datai-models-starter/ # 模型启动器 + ├── datai-quartz/ # Quartz 定时任务 + ├── datai-form/ # 表单管理 + ├── datai-message/ # 消息管理 + ├── datai-generator/ # 代码生成器 + ├── datai-online/ # 在线用户 + └── datai-flowable/ # 工作流 ``` -## 内置功能 +## 核心功能 -1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。 -2. 部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。 -3. 岗位管理:配置系统用户所属担任职务。 -4. 菜单管理:配置系统菜单,操作权限,按钮权限标识等。 -5. 角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。 -6. 字典管理:对系统中经常使用的一些较为固定的数据进行维护。 -7. 参数管理:对系统动态配置常用参数。 -8. 通知公告:系统通知公告信息发布维护。 -9. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。 -10. 登录日志:系统登录日志记录查询包含登录异常。 -11. 在线用户:当前系统中活跃用户状态监控。 -12. 定时任务:在线(添加、修改、删除)任务调度包含执行结果日志。 -13. 代码生成:前后端代码的生成(java、html、xml、sql)支持CRUD下载 。 -14. 系统接口:根据业务代码自动生成相关的api接口文档。 -15. 服务监控:监视当前系统CPU、内存、磁盘、堆栈等相关信息。 -16. 缓存监控:对系统的缓存信息查询,命令统计等。 -17. 在线构建器:拖动表单元素生成相应的HTML代码。 -18. 连接池监视:监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈。 -19. 支付场景 -20. 第三方登录场景 -21. 中间件场景 +### Salesforce 集成模块(当前实现) -## 演示图 +提供与 Salesforce 源系统的数据集成解决方案,支持从 Salesforce 同步数据到本地数据库。 -### 新加功能和增强功能演示 +#### 认证管理 +- **多种登录方式**: OAuth2、SessionId、CLI、Legacy Credential +- **会话管理**: 登录会话的创建、查询和销毁 +- **历史记录**: 登录历史记录的查询和统计 - - - - - - - - - - - - - - - - - -
+#### 数据同步(源系统 → 本地) +- **Bulk API v1/v2**: 批量数据同步,支持大规模数据导入导出 +- **Partner API**: SOAP API 数据交互,支持 CRUD 操作 +- **REST API**: RESTful API 数据交互 +- **CDC 实时同步**: Change Data Capture 实时数据同步 -### 原有功能演示 +#### 元数据管理 +- **对象管理**: Salesforce 对象的查询、同步和结构创建 +- **字段管理**: 对象字段的查询和管理 +- **Picklist 管理**: Picklist 值的查询和管理 +- **Lookup 过滤器**: Lookup 过滤器的查询和管理 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+#### 批次管理 +- **批次创建**: 创建数据同步批次 +- **批次数据插入**: 将本地数据插入到 Salesforce,保存返回 ID +- **批次数据更新**: 使用保存的 ID 更新 Salesforce 数据 +- **批次重试**: 失败批次的重试机制 +- **批次历史**: 批次操作历史记录 -# 联系我们: +#### 实时同步 +- **Pub/Sub API**: Salesforce Event Bus 实时数据订阅 +- **事件处理**: CDC 事件的接收和处理 +- **数据同步**: 实时数据同步到本地数据库 +- **服务管理**: 实时同步服务的启动、停止和重启 -QQ交流群:744785891 +#### 监控日志 +- **API 调用日志**: Salesforce API 调用记录和统计 +- **同步日志**: 数据同步操作日志 +- **速率限制监控**: Salesforce API 速率限制监控 +- **实时同步日志**: CDC 实时同步日志 + +### 开发计划 + +#### 1. 数据推送到目标 Salesforce 系统 +- **目标系统认证**: 支持目标 Salesforce 系统的多种认证方式 +- **数据推送**: 支持将本地数据批量推送到目标 Salesforce 系统 +- **双向同步**: 实现源系统 → 本地 → 目标系统的数据流转 +- **数据映射**: 支持源系统和目标系统之间的字段映射 +- **推送监控**: 数据推送任务的监控和日志记录 + +#### 2. 源 Salesforce 系统文件下载 +- **文件元数据**: 获取 Salesforce 附件和文档的元数据信息 +- **文件下载**: 支持批量下载 Salesforce 中的文件(Attachment、Document、ContentVersion) +- **文件存储**: 支持本地存储、MinIO、阿里云 OSS 等多种存储方式 +- **文件同步**: 文件与数据的关联同步 +- **下载监控**: 文件下载任务的监控和进度追踪 + +#### 3. User 权限同步集成系统 +- **权限元数据**: 同步 Salesforce User、Profile、PermissionSet、Role 等权限相关对象 +- **权限映射**: 将 Salesforce 权限映射到系统用户权限 +- **数据权限控制**: 基于用户权限控制数据访问范围 +- **权限同步**: 实时同步用户权限变更 +- **权限审计**: 权限变更的审计日志 + +#### 4. 集成可视化报表插件 +- **报表设计器**: 可视化拖拽式报表设计器,支持自定义报表布局 +- **数据源配置**: 支持从本地数据库和 Salesforce 系统配置数据源 +- **图表组件**: 提供丰富的图表类型(柱状图、折线图、饼图、表格等) +- **数据筛选**: 支持动态筛选条件和参数化查询 +- **报表导出**: 支持导出为 PDF、Excel、图片等多种格式 +- **报表分享**: 支持报表分享和权限控制 +- **实时刷新**: 支持报表数据的实时刷新和定时更新 +- **报表模板**: 提供常用报表模板,快速生成标准报表 + +### 认证场景模块 + +- **OAuth 集成**: JustAuth 和微信 OAuth 登录 +- **双因子认证**: 邮箱和手机双因子认证 +- **用户管理**: OAuth 用户信息管理 + + +### 文件场景模块 + +- **本地存储**: 本地文件系统存储 +- **MinIO 存储**: MinIO 对象存储 +- **阿里云 OSS**: 阿里云对象存储服务 +- **文件管理**: 文件上传、下载和管理 + +### 插件模块 + +- **MyBatis Plus**: MyBatis 增强工具 +- **Redis 缓存**: Redis 分布式缓存 +- **Ehcache 缓存**: Ehcache 本地缓存 +- **RabbitMQ**: 消息队列中间件 +- **WebSocket**: WebSocket 实时通信 +- **Netty**: Netty 网络框架 +- **Atomikos**: 分布式事务管理 + +### 模型模块 + +- **Quartz**: 定时任务调度 +- **表单管理**: 动态表单生成和管理 +- **消息管理**: 系统消息管理 +- **代码生成器**: 代码自动生成工具 +- **在线用户**: 在线用户管理 +- **工作流**: Flowable 工作流引擎 + +## 快速开始 + +### 环境要求 + +- JDK 21+ +- Maven 3.6+ +- MySQL 8.3.0+ 或 PostgreSQL 42.7.7+ +- Redis(可选,用于缓存) + +### 配置说明 + +#### 关键配置项 + +| 配置项 | 默认值 | 说明 | +|--------|--------|------| +| server.port | 8080 | 服务端口 | +| datai.profile | D:/datai/uploadPath | 文件上传路径 | +| datai.fileServer | local | 文件服务类型(local/minio/oss) | +| datai.fileMaxSize | 50 | 文件上传最大大小(MB) | +| spring.cache.type | redis | 缓存类型(redis/jcache) | +| token.expireTime | 1440 | Token 有效期(分钟) | +| pagehelper.helperDialect | mysql | 数据库方言 | + +#### 启用配置文件 + +- `application-druid.yml`: 数据库连接池配置 +- `application-file.yml`: 文件服务配置 +- `application-auth.yml`: 认证配置 +- `application-pay.yml`: 支付配置 +- `application-plugins.yml`: 插件配置 +- `application-model.yml`: 模型配置 +- `application-apidoc.yml`: API 文档配置 + +### 构建项目 + +```bash +mvn clean install +``` + +### 运行项目 + +```bash +cd datai-admin +mvn spring-boot:run +``` + +启动成功后,访问: +- 应用地址: http://localhost:8080 +- API 文档: http://localhost:8080/doc.html + +## 文档 + +### 项目规则 + +项目采用基于单一真源 (Single Source of Truth) 的 Workflow,所有项目相关文档和决策均集中在 `docs/` 目录中。 + +### Salesforce 模块文档 + +详细文档请参考 [Salesforce 集成模块文档中心](datai-scenes/datai-scene-salesforce/docs/index.md) + +- **唯一入口**: [datai-scene-salesforce/docs/index.md](datai-scenes/datai-scene-salesforce/docs/index.md) +- **架构看板**: [datai-scene-salesforce/docs/Authentication.canvas](datai-scenes/datai-scene-salesforce/docs/Authentication.canvas) +- **接口文档**: [datai-scene-salesforce/docs/api-docs/](datai-scenes/datai-scene-salesforce/docs/api-docs/) + +### 文档目录 + +- **需求文档**: [datai-scenes/datai-scene-salesforce/docs/requirements/](datai-scenes/datai-scene-salesforce/docs/requirements/) +- **设计文档**: [datai-scenes/datai-scene-salesforce/docs/design/](datai-scenes/datai-scene-salesforce/docs/design/) +- **架构决策**: [datai-scenes/datai-scene-salesforce/docs/decisions/adr/](datai-scenes/datai-scene-salesforce/docs/decisions/adr/) +- **提示词库**: [datai-scenes/datai-scene-salesforce/docs/prompts/](datai-scenes/datai-scene-salesforce/docs/prompts/) +- **会话记录**: [datai-scenes/datai-scene-salesforce/docs/sessions/](datai-scenes/datai-scene-salesforce/docs/sessions/) +- **变更日志**: [datai-scenes/datai-scene-salesforce/docs/changelog/](datai-scenes/datai-scene-salesforce/docs/changelog/) + +## 贡献指南 + +请参考 [CONTRIBUTING.md](CONTRIBUTING.md) 了解如何参与项目贡献。 + +## 变更日志 + +请参考 [CHANGELOG.md](CHANGELOG.md) 查看项目变更记录。 + +## 许可证 + +本项目基于若依Geek框架开发,遵循相应的开源许可证。 + +## 联系方式 + +- 项目地址: https://gitee.com/geek-xd/datai-springboot3.git +- 文档中心: [datai-scenes/datai-scene-salesforce/docs/index.md](datai-scenes/datai-scene-salesforce/docs/index.md) diff --git a/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/controller/DataiIntegrationBatchController.java b/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/controller/DataiIntegrationBatchController.java index 4075fc6c..a98a4b95 100644 --- a/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/controller/DataiIntegrationBatchController.java +++ b/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/controller/DataiIntegrationBatchController.java @@ -28,6 +28,7 @@ import com.datai.common.utils.poi.ExcelUtil; import com.datai.common.core.page.TableDataInfo; import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.Operation; +import lombok.extern.slf4j.Slf4j; /** * 数据批次Controller @@ -38,6 +39,7 @@ import io.swagger.v3.oas.annotations.Operation; @RestController @RequestMapping("/integration/batch") @Tag(name = "【数据批次】管理") +@Slf4j public class DataiIntegrationBatchController extends BaseController { @Autowired @@ -180,4 +182,104 @@ public class DataiIntegrationBatchController extends BaseController return error((String) result.get("message")); } } + + /** + * 批次数据插入目标系统 + * + * 该方法用于将本地数据库中的指定批次数据插入到目标Salesforce系统 + * 插入操作会查询本地表中符合批次条件的数据,然后通过SOAP API插入到目标系统 + * 插入成功后,会将返回的ID保存到new_id字段,并更新is_insert字段 + * + * @param id 批次ID,用于标识需要插入的批次 + * @param targetOrgType 目标ORG类型,用于标识目标Salesforce系统 + * @return AjaxResult 插入结果,包含成功/失败状态、插入数据量、耗时等信息 + * - 成功时返回:success=true, message="批次数据插入完成", 以及详细的插入信息 + * - 失败时返回:success=false, message=错误信息 + */ + @Operation(summary = "批次数据插入目标系统") + @PreAuthorize("@ss.hasPermi('integration:batch:insertData')") + @Log(title = "数据批次", businessType = BusinessType.INSERT) + @PostMapping("/{id}/insertData") + public AjaxResult insertBatchDataToTarget(@PathVariable("id") Integer id, + @org.springframework.web.bind.annotation.RequestParam("targetOrgType") String targetOrgType) + { + if (id == null) { + log.error("批次ID为空,无法插入数据"); + return error("批次ID不能为空"); + } + + if (targetOrgType == null || targetOrgType.trim().isEmpty()) { + log.error("目标ORG类型为空,无法插入数据"); + return error("目标ORG类型不能为空"); + } + + try { + log.info("开始插入批次数据到目标系统,批次ID: {}, 目标ORG类型: {}", id, targetOrgType); + + Map result = dataiIntegrationBatchService.insertBatchDataToTarget(id, targetOrgType); + + if ((Boolean) result.get("success")) { + log.info("批次数据插入成功,批次ID: {}, 目标ORG类型: {}", id, targetOrgType); + return success(result); + } else { + log.error("批次数据插入失败,批次ID: {}, 目标ORG类型: {}, 错误信息: {}", + id, targetOrgType, result.get("message")); + return error((String) result.get("message")); + } + + } catch (Exception e) { + log.error("插入批次数据时发生异常,批次ID: {}, 目标ORG类型: {}", id, targetOrgType, e); + return error("插入批次数据时发生异常: " + e.getMessage()); + } + } + + /** + * 批次数据更新目标系统 + * + * 该方法用于将本地数据库中的指定批次数据更新到目标Salesforce系统 + * 更新操作会查询本地表中符合批次条件的数据,然后通过SOAP API更新到目标系统 + * 更新时使用new_id作为目标系统的ID,更新成功后,更新is_update字段 + * + * @param id 批次ID,用于标识需要更新的批次 + * @param targetOrgType 目标ORG类型,用于标识目标Salesforce系统 + * @return AjaxResult 更新结果,包含成功/失败状态、更新数据量、耗时等信息 + * - 成功时返回:success=true, message="批次数据更新完成", 以及详细的更新信息 + * - 失败时返回:success=false, message=错误信息 + */ + @Operation(summary = "批次数据更新目标系统") + @PreAuthorize("@ss.hasPermi('integration:batch:updateData')") + @Log(title = "数据批次", businessType = BusinessType.UPDATE) + @PostMapping("/{id}/updateData") + public AjaxResult updateBatchDataToTarget(@PathVariable("id") Integer id, + @org.springframework.web.bind.annotation.RequestParam("targetOrgType") String targetOrgType) + { + if (id == null) { + log.error("批次ID为空,无法更新数据"); + return error("批次ID不能为空"); + } + + if (targetOrgType == null || targetOrgType.trim().isEmpty()) { + log.error("目标ORG类型为空,无法更新数据"); + return error("目标ORG类型不能为空"); + } + + try { + log.info("开始更新批次数据到目标系统,批次ID: {}, 目标ORG类型: {}", id, targetOrgType); + + Map result = dataiIntegrationBatchService.updateBatchDataToTarget(id, targetOrgType); + + if ((Boolean) result.get("success")) { + log.info("批次数据更新成功,批次ID: {}, 目标ORG类型: {}", id, targetOrgType); + return success(result); + } else { + log.error("批次数据更新失败,批次ID: {}, 目标ORG类型: {}, 错误信息: {}", + id, targetOrgType, result.get("message")); + return error((String) result.get("message")); + } + + } catch (Exception e) { + log.error("更新批次数据时发生异常,批次ID: {}, 目标ORG类型: {}", id, targetOrgType, e); + return error("更新批次数据时发生异常: " + e.getMessage()); + } + } } diff --git a/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/controller/DataiIntegrationObjectController.java b/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/controller/DataiIntegrationObjectController.java index 9f56f0cc..50135c1f 100644 --- a/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/controller/DataiIntegrationObjectController.java +++ b/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/controller/DataiIntegrationObjectController.java @@ -318,4 +318,79 @@ public class DataiIntegrationObjectController extends BaseController return error("同步多个对象数据时发生异常: " + e.getMessage()); } } + + @Operation(summary = "推送单对象数据到目标系统") + @PreAuthorize("@ss.hasPermi('integration:object:pushData')") + @Log(title = "对象同步控制", businessType = BusinessType.UPDATE) + @PostMapping("/{id}/pushData") + public AjaxResult pushObjectData(@PathVariable("id") Integer id, + @org.springframework.web.bind.annotation.RequestParam("targetOrgType") String targetOrgType) + { + if (id == null) { + log.error("对象ID为空,无法推送数据"); + return error("对象ID不能为空"); + } + + if (targetOrgType == null || targetOrgType.trim().isEmpty()) { + log.error("目标ORG类型为空,无法推送数据"); + return error("目标ORG类型不能为空"); + } + + try { + log.info("开始推送对象数据到目标系统,对象ID: {}, 目标ORG类型: {}", id, targetOrgType); + + Map result = dataiIntegrationObjectService.pushSingleObjectDataToTarget(id, targetOrgType); + + if ((Boolean) result.get("success")) { + log.info("对象数据推送成功,对象ID: {}, 目标ORG类型: {}", id, targetOrgType); + return success(result); + } else { + log.error("对象数据推送失败,对象ID: {}, 目标ORG类型: {}, 错误信息: {}", + id, targetOrgType, result.get("message")); + return error((String) result.get("message")); + } + + } catch (Exception e) { + log.error("推送对象数据时发生异常,对象ID: {}, 目标ORG类型: {}", id, targetOrgType, e); + return error("推送对象数据时发生异常: " + e.getMessage()); + } + } + + + @Operation(summary = "推送多个对象数据到目标系统") + @PreAuthorize("@ss.hasPermi('integration:object:pushData')") + @Log(title = "对象同步控制", businessType = BusinessType.UPDATE) + @PostMapping("/pushMultipleData") + public AjaxResult pushMultipleObjectData(@RequestBody Integer[] ids, + @org.springframework.web.bind.annotation.RequestParam("targetOrgType") String targetOrgType) + { + if (ids == null || ids.length == 0) { + log.error("对象ID数组为空,无法推送数据"); + return error("对象ID不能为空"); + } + + if (targetOrgType == null || targetOrgType.trim().isEmpty()) { + log.error("目标ORG类型为空,无法推送数据"); + return error("目标ORG类型不能为空"); + } + + try { + log.info("开始推送多个对象数据到目标系统,对象ID数量: {}, 目标ORG类型: {}", ids.length, targetOrgType); + + Map result = dataiIntegrationObjectService.pushMultipleObjectDataToTarget(ids, targetOrgType); + + if ((Boolean) result.get("success")) { + log.info("多对象数据推送完成,成功数量: {}, 失败数量: {}", + result.get("successCount"), result.get("failureCount")); + return success(result); + } else { + log.error("多对象数据推送失败,错误信息: {}", result.get("message")); + return error((String) result.get("message")); + } + + } catch (Exception e) { + log.error("推送多个对象数据时发生异常", e); + return error("推送多个对象数据时发生异常: " + e.getMessage()); + } + } } diff --git a/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/model/domain/DataiIntegrationBatch.java b/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/model/domain/DataiIntegrationBatch.java index 755c21d6..d49e6e48 100644 --- a/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/model/domain/DataiIntegrationBatch.java +++ b/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/model/domain/DataiIntegrationBatch.java @@ -9,7 +9,7 @@ import com.datai.common.annotation.Excel; import com.datai.common.core.domain.BaseEntity; /** - * 数据批次对象 datai_integration_batch + * 数据批次对象 datai_integr ation_batch * * 字段更新规则: * 1. 创建批次时设置的字段(后续不允许修改): @@ -30,6 +30,12 @@ import com.datai.common.core.domain.BaseEntity; * - lastSyncTime: 最后同步时间(每次同步时更新) * - updateTime: 更新时间(每次更新时自动设置) * + * 3. 推送时更新的字段: + * - sfInsertNum: SF插入数据量(每次推送时更新) + * - sfUpdateNum: SF更新数据量(每次推送时更新) + * - firstPushTime: 首次推送时间(仅在首次推送时设置) + * - lastPushTime: 最后推送时间(每次推送时更新) + * * @author datai * @date 2026-01-01 */ @@ -67,6 +73,16 @@ public class DataiIntegrationBatch extends BaseEntity @Excel(name = "本地数据量") private Integer dbNum; + /** SF插入数据量 */ + @Schema(title = "SF插入数据量") + @Excel(name = "SF插入数据量") + private Integer sfInsertNum; + + /** SF更新数据量 */ + @Schema(title = "SF更新数据量") + @Excel(name = "SF更新数据量") + private Integer sfUpdateNum; + /** 同步类型 */ @Schema(title = "同步类型") @Excel(name = "同步类型") @@ -101,6 +117,16 @@ public class DataiIntegrationBatch extends BaseEntity @Schema(title = "最后同步时间") @Excel(name = "最后同步时间") private LocalDateTime lastSyncTime; + + /** 首次推送时间 */ + @Schema(title = "首次推送时间") + @Excel(name = "首次推送时间") + private LocalDateTime firstPushTime; + + /** 最后推送时间 */ + @Schema(title = "最后推送时间") + @Excel(name = "最后推送时间") + private LocalDateTime lastPushTime; public void setId(Integer id) { this.id = id; @@ -167,6 +193,28 @@ public class DataiIntegrationBatch extends BaseEntity } + public void setSfInsertNum(Integer sfInsertNum) + { + this.sfInsertNum = sfInsertNum; + } + + public Integer getSfInsertNum() + { + return sfInsertNum; + } + + + public void setSfUpdateNum(Integer sfUpdateNum) + { + this.sfUpdateNum = sfUpdateNum; + } + + public Integer getSfUpdateNum() + { + return sfUpdateNum; + } + + public void setSyncType(String syncType) { this.syncType = syncType; @@ -244,6 +292,28 @@ public class DataiIntegrationBatch extends BaseEntity } + public void setFirstPushTime(LocalDateTime firstPushTime) + { + this.firstPushTime = firstPushTime; + } + + public LocalDateTime getFirstPushTime() + { + return firstPushTime; + } + + + public void setLastPushTime(LocalDateTime lastPushTime) + { + this.lastPushTime = lastPushTime; + } + + public LocalDateTime getLastPushTime() + { + return lastPushTime; + } + + @Override public String toString() { @@ -254,6 +324,8 @@ public class DataiIntegrationBatch extends BaseEntity .append("label", getLabel()) .append("sfNum", getSfNum()) .append("dbNum", getDbNum()) + .append("sfInsertNum", getSfInsertNum()) + .append("sfUpdateNum", getSfUpdateNum()) .append("syncType", getSyncType()) .append("batchField", getBatchField()) .append("syncStatus", getSyncStatus()) @@ -261,6 +333,8 @@ public class DataiIntegrationBatch extends BaseEntity .append("syncEndDate", getSyncEndDate()) .append("firstSyncTime", getFirstSyncTime()) .append("lastSyncTime", getLastSyncTime()) + .append("firstPushTime", getFirstPushTime()) + .append("lastPushTime", getLastPushTime()) .append("remark", getRemark()) .append("createBy", getCreateBy()) .append("createTime", getCreateTime()) diff --git a/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/model/dto/DataiIntegrationBatchDto.java b/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/model/dto/DataiIntegrationBatchDto.java index 0b76af9a..7fd09c74 100644 --- a/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/model/dto/DataiIntegrationBatchDto.java +++ b/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/model/dto/DataiIntegrationBatchDto.java @@ -42,6 +42,12 @@ public class DataiIntegrationBatchDto implements Serializable /** 本地数据量 */ private Integer dbNum; + /** SF插入数据量 */ + private Integer sfInsertNum; + + /** SF更新数据量 */ + private Integer sfUpdateNum; + /** 同步类型 */ private String syncType; @@ -63,6 +69,12 @@ public class DataiIntegrationBatchDto implements Serializable /** 最后同步时间 */ private LocalDateTime lastSyncTime; + /** 首次推送时间 */ + private LocalDateTime firstPushTime; + + /** 最后推送时间 */ + private LocalDateTime lastPushTime; + /** 备注 */ private String remark; diff --git a/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/model/vo/DataiIntegrationBatchVo.java b/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/model/vo/DataiIntegrationBatchVo.java index 4b3e330c..7ab7b174 100644 --- a/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/model/vo/DataiIntegrationBatchVo.java +++ b/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/model/vo/DataiIntegrationBatchVo.java @@ -37,6 +37,12 @@ public class DataiIntegrationBatchVo implements Serializable { /** 本地数据量 */ private Integer dbNum; + /** SF插入数据量 */ + private Integer sfInsertNum; + + /** SF更新数据量 */ + private Integer sfUpdateNum; + /** 同步类型 */ private String syncType; @@ -58,6 +64,12 @@ public class DataiIntegrationBatchVo implements Serializable { /** 最后同步时间 */ private LocalDateTime lastSyncTime; + /** 首次推送时间 */ + private LocalDateTime firstPushTime; + + /** 最后推送时间 */ + private LocalDateTime lastPushTime; + /** 备注 */ private String remark; diff --git a/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/service/IDataiIntegrationBatchService.java b/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/service/IDataiIntegrationBatchService.java index 79482df1..5aec5eae 100644 --- a/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/service/IDataiIntegrationBatchService.java +++ b/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/service/IDataiIntegrationBatchService.java @@ -90,4 +90,22 @@ public interface IDataiIntegrationBatchService * @return 所有批次统计信息 */ public Map getAllBatchStatistics(); + + /** + * 批次数据插入目标系统 + * + * @param id 批次ID + * @param targetOrgType 目标ORG类型 + * @return 插入结果 + */ + public Map insertBatchDataToTarget(Integer id, String targetOrgType); + + /** + * 批次数据更新目标系统 + * + * @param id 批次ID + * @param targetOrgType 目标ORG类型 + * @return 更新结果 + */ + public Map updateBatchDataToTarget(Integer id, String targetOrgType); } diff --git a/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/service/IDataiIntegrationObjectService.java b/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/service/IDataiIntegrationObjectService.java index 35211f08..c052f0b5 100644 --- a/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/service/IDataiIntegrationObjectService.java +++ b/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/service/IDataiIntegrationObjectService.java @@ -136,5 +136,23 @@ public interface IDataiIntegrationObjectService */ public Map syncMultipleObjectData(Integer[] ids); + /** + * 推送单对象数据到目标系统 + * + * @param id 对象ID + * @param targetOrgType 目标ORG类型 + * @return 推送结果 + */ + public Map pushSingleObjectDataToTarget(Integer id, String targetOrgType); + + /** + * 推送多个对象数据到目标系统 + * + * @param ids 对象ID集合 + * @param targetOrgType 目标ORG类型 + * @return 推送结果 + */ + public Map pushMultipleObjectDataToTarget(Integer[] ids, String targetOrgType); + Set getRealtimeSyncObjects(); } diff --git a/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/service/impl/DataiIntegrationBatchServiceImpl.java b/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/service/impl/DataiIntegrationBatchServiceImpl.java index d3e0ee8b..9e78247f 100644 --- a/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/service/impl/DataiIntegrationBatchServiceImpl.java +++ b/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/service/impl/DataiIntegrationBatchServiceImpl.java @@ -993,4 +993,400 @@ public class DataiIntegrationBatchServiceImpl implements IDataiIntegrationBatchS return statistics; } + + @Override + public Map insertBatchDataToTarget(Integer id, String targetOrgType) { + Map result = new HashMap<>(); + long startTime = System.currentTimeMillis(); + + try { + log.info("开始插入批次数据到目标系统,批次ID: {}, 目标ORG类型: {}", id, targetOrgType); + + if (id == null) { + log.error("批次ID不能为空"); + result.put("success", false); + result.put("message", "批次ID不能为空"); + return result; + } + + if (targetOrgType == null || targetOrgType.trim().isEmpty()) { + log.error("目标ORG类型不能为空"); + result.put("success", false); + result.put("message", "目标ORG类型不能为空"); + return result; + } + + DataiIntegrationBatch batch = selectDataiIntegrationBatchById(id); + if (batch == null) { + log.error("批次不存在,批次ID: {}", id); + result.put("success", false); + result.put("message", "批次不存在"); + return result; + } + + String objectApi = batch.getApi(); + if (objectApi == null || objectApi.trim().isEmpty()) { + log.error("批次对象API为空,批次ID: {}", id); + result.put("success", false); + result.put("message", "批次对象API不能为空"); + return result; + } + + String tableName = objectApi.trim(); + if (customMapper.checkTable(tableName) == null) { + log.error("本地表不存在,表名: {}", tableName); + result.put("success", false); + result.put("message", "本地表不存在: " + tableName); + return result; + } + + log.info("准备插入批次 {} 的对象 {} 数据到目标系统", id, objectApi); + + IPartnerV1Connection targetConnection = null; + try { + targetConnection = soapConnectionFactory.getConnection(targetOrgType); + if (targetConnection == null) { + log.error("无法获取目标系统连接,ORG类型: {}", targetOrgType); + result.put("success", false); + result.put("message", "无法获取目标系统连接"); + return result; + } + + DescribeSObjectResult objDetail = targetConnection.describeSObject(objectApi); + if (objDetail == null) { + log.error("目标系统不存在该对象,对象API: {}", objectApi); + result.put("success", false); + result.put("message", "目标系统不存在该对象: " + objectApi); + return result; + } + + String batchField = batch.getBatchField(); + String whereClause = ""; + if (batchField != null && !batchField.trim().isEmpty()) { + whereClause = "WHERE " + batchField.trim() + " = '" + batch.getBatchValue() + "'"; + } + + log.info("开始查询本地批次数据,表名: {}, 条件: {}", tableName, whereClause); + List> dataList = customMapper.list("*", tableName, whereClause); + + if (dataList == null || dataList.isEmpty()) { + log.warn("本地批次没有数据,表名: {}, 条件: {}", tableName, whereClause); + result.put("success", false); + result.put("message", "本地批次没有数据"); + return result; + } + + log.info("查询到 {} 条本地批次数据,准备插入到目标系统", dataList.size()); + + int successCount = 0; + int failureCount = 0; + List> failedRecords = new ArrayList<>(); + + for (int i = 0; i < dataList.size(); i++) { + Map dataMap = dataList.get(i); + try { + String localId = (String) dataMap.get("id"); + if (localId == null || localId.isEmpty()) { + log.warn("记录ID为空,跳过该记录,索引: {}", i); + failureCount++; + Map failedRecord = new HashMap<>(); + failedRecord.put("index", i); + failedRecord.put("reason", "记录ID为空"); + failedRecords.add(failedRecord); + continue; + } + + Map fieldsToInsert = new HashMap<>(); + for (String key : dataMap.keySet()) { + if (!"id".equalsIgnoreCase(key) && !"new_id".equalsIgnoreCase(key) && + !"is_insert".equalsIgnoreCase(key) && !"is_update".equalsIgnoreCase(key) && + dataMap.get(key) != null) { + fieldsToInsert.put(key, dataMap.get(key)); + } + } + + if (fieldsToInsert.isEmpty()) { + log.warn("记录没有需要插入的字段,跳过该记录,ID: {}", localId); + continue; + } + + SObject sObject = new SObject(); + sObject.setType(objectApi); + for (Map.Entry entry : fieldsToInsert.entrySet()) { + sObject.setField(entry.getKey(), entry.getValue()); + } + + SaveResult[] saveResults = targetConnection.create(new SObject[]{sObject}); + if (saveResults != null && saveResults.length > 0 && saveResults[0].isSuccess()) { + String newId = saveResults[0].getId(); + + List> updateMaps = new ArrayList<>(); + Map newIdMap = new HashMap<>(); + newIdMap.put("key", "new_id"); + newIdMap.put("value", newId); + updateMaps.add(newIdMap); + + Map isInsertMap = new HashMap<>(); + isInsertMap.put("key", "is_insert"); + isInsertMap.put("value", 1); + updateMaps.add(isInsertMap); + + customMapper.updateById(tableName, updateMaps, localId); + + successCount++; + log.debug("记录插入成功,本地ID: {}, 新ID: {}", localId, newId); + } else { + String errorMsg = saveResults != null && saveResults.length > 0 ? + saveResults[0].getErrors()[0].getMessage() : "未知错误"; + log.error("记录插入失败,本地ID: {}, 错误: {}", localId, errorMsg); + failureCount++; + Map failedRecord = new HashMap<>(); + failedRecord.put("index", i); + failedRecord.put("localId", localId); + failedRecord.put("reason", errorMsg); + failedRecords.add(failedRecord); + } + + if ((i + 1) % 100 == 0) { + log.info("已插入 {}/{} 条记录", i + 1, dataList.size()); + } + + } catch (Exception e) { + log.error("插入记录失败,索引: {}, 错误: {}", i, e.getMessage()); + failureCount++; + Map failedRecord = new HashMap<>(); + failedRecord.put("index", i); + failedRecord.put("reason", e.getMessage()); + failedRecords.add(failedRecord); + } + } + + long endTime = System.currentTimeMillis(); + long duration = endTime - startTime; + + result.put("success", true); + result.put("message", "批次数据插入完成"); + result.put("batchId", id); + result.put("api", objectApi); + result.put("label", batch.getLabel()); + result.put("targetOrgType", targetOrgType); + result.put("totalCount", dataList.size()); + result.put("successCount", successCount); + result.put("failureCount", failureCount); + result.put("duration", duration); + result.put("failedRecords", failedRecords); + + log.info("批次数据插入完成,批次ID: {}, 对象API: {}, 总数: {}, 成功: {}, 失败: {}, 耗时: {}ms", + id, objectApi, dataList.size(), successCount, failureCount, duration); + + } finally { + if (targetConnection != null) { + try { + targetConnection.logout(); + } catch (Exception e) { + log.error("关闭目标系统连接失败", e); + } + } + } + + } catch (Exception e) { + log.error("插入批次数据到目标系统失败,批次ID: {}", id, e); + result.put("success", false); + result.put("message", "插入批次数据到目标系统失败: " + e.getMessage()); + } + + return result; + } + + public Map updateBatchDataToTarget(Integer id, String targetOrgType) { + Map result = new HashMap<>(); + long startTime = System.currentTimeMillis(); + + try { + log.info("开始更新批次数据到目标系统,批次ID: {}, 目标ORG类型: {}", id, targetOrgType); + + if (id == null) { + log.error("批次ID不能为空"); + result.put("success", false); + result.put("message", "批次ID不能为空"); + return result; + } + + if (targetOrgType == null || targetOrgType.trim().isEmpty()) { + log.error("目标ORG类型不能为空"); + result.put("success", false); + result.put("message", "目标ORG类型不能为空"); + return result; + } + + DataiIntegrationBatch batch = selectDataiIntegrationBatchById(id); + if (batch == null) { + log.error("批次不存在,批次ID: {}", id); + result.put("success", false); + result.put("message", "批次不存在"); + return result; + } + + String objectApi = batch.getApi(); + if (objectApi == null || objectApi.trim().isEmpty()) { + log.error("批次对象API为空,批次ID: {}", id); + result.put("success", false); + result.put("message", "批次对象API不能为空"); + return result; + } + + String tableName = objectApi.trim(); + if (customMapper.checkTable(tableName) == null) { + log.error("本地表不存在,表名: {}", tableName); + result.put("success", false); + result.put("message", "本地表不存在: " + tableName); + return result; + } + + log.info("准备更新批次 {} 的对象 {} 数据到目标系统", id, objectApi); + + IPartnerV1Connection targetConnection = null; + try { + targetConnection = soapConnectionFactory.getConnection(targetOrgType); + if (targetConnection == null) { + log.error("无法获取目标系统连接,ORG类型: {}", targetOrgType); + result.put("success", false); + result.put("message", "无法获取目标系统连接"); + return result; + } + + DescribeSObjectResult objDetail = targetConnection.describeSObject(objectApi); + if (objDetail == null) { + log.error("目标系统不存在该对象,对象API: {}", objectApi); + result.put("success", false); + result.put("message", "目标系统不存在该对象: " + objectApi); + return result; + } + + String batchField = batch.getBatchField(); + String whereClause = ""; + if (batchField != null && !batchField.trim().isEmpty()) { + whereClause = "WHERE " + batchField.trim() + " = '" + batch.getBatchValue() + "'"; + } + + log.info("开始查询本地批次数据,表名: {}, 条件: {}", tableName, whereClause); + List> dataList = customMapper.list("*", tableName, whereClause); + + if (dataList == null || dataList.isEmpty()) { + log.warn("本地批次没有数据,表名: {}, 条件: {}", tableName, whereClause); + result.put("success", false); + result.put("message", "本地批次没有数据"); + return result; + } + + log.info("查询到 {} 条本地批次数据,准备更新到目标系统", dataList.size()); + + int successCount = 0; + int failureCount = 0; + List> failedRecords = new ArrayList<>(); + + for (int i = 0; i < dataList.size(); i++) { + Map dataMap = dataList.get(i); + try { + String localId = (String) dataMap.get("id"); + String newId = (String) dataMap.get("new_id"); + + if (localId == null || localId.isEmpty()) { + log.warn("记录ID为空,跳过该记录,索引: {}", i); + failureCount++; + Map failedRecord = new HashMap<>(); + failedRecord.put("index", i); + failedRecord.put("reason", "记录ID为空"); + failedRecords.add(failedRecord); + continue; + } + + if (newId == null || newId.isEmpty()) { + log.warn("记录new_id为空,跳过该记录,本地ID: {}", localId); + failureCount++; + Map failedRecord = new HashMap<>(); + failedRecord.put("index", i); + failedRecord.put("localId", localId); + failedRecord.put("reason", "new_id为空,请先执行插入操作"); + failedRecords.add(failedRecord); + continue; + } + + Map fieldsToUpdate = new HashMap<>(); + for (String key : dataMap.keySet()) { + if (!"id".equalsIgnoreCase(key) && !"new_id".equalsIgnoreCase(key) && + !"is_insert".equalsIgnoreCase(key) && !"is_update".equalsIgnoreCase(key) && + dataMap.get(key) != null) { + fieldsToUpdate.put(key, dataMap.get(key)); + } + } + + if (fieldsToUpdate.isEmpty()) { + log.warn("记录没有需要更新的字段,跳过该记录,本地ID: {}", localId); + continue; + } + + targetConnection.update(objectApi, newId, fieldsToUpdate); + + List> updateMaps = new ArrayList<>(); + Map isUpdateMap = new HashMap<>(); + isUpdateMap.put("key", "is_update"); + isUpdateMap.put("value", 1); + updateMaps.add(isUpdateMap); + + customMapper.updateById(tableName, updateMaps, localId); + + successCount++; + log.debug("记录更新成功,本地ID: {}, 目标ID: {}", localId, newId); + + if ((i + 1) % 100 == 0) { + log.info("已更新 {}/{} 条记录", i + 1, dataList.size()); + } + + } catch (Exception e) { + log.error("更新记录失败,索引: {}, 错误: {}", i, e.getMessage()); + failureCount++; + Map failedRecord = new HashMap<>(); + failedRecord.put("index", i); + failedRecord.put("reason", e.getMessage()); + failedRecords.add(failedRecord); + } + } + + long endTime = System.currentTimeMillis(); + long duration = endTime - startTime; + + result.put("success", true); + result.put("message", "批次数据更新完成"); + result.put("batchId", id); + result.put("api", objectApi); + result.put("label", batch.getLabel()); + result.put("targetOrgType", targetOrgType); + result.put("totalCount", dataList.size()); + result.put("successCount", successCount); + result.put("failureCount", failureCount); + result.put("duration", duration); + result.put("failedRecords", failedRecords); + + log.info("批次数据更新完成,批次ID: {}, 对象API: {}, 总数: {}, 成功: {}, 失败: {}, 耗时: {}ms", + id, objectApi, dataList.size(), successCount, failureCount, duration); + + } finally { + if (targetConnection != null) { + try { + targetConnection.logout(); + } catch (Exception e) { + log.error("关闭目标系统连接失败", e); + } + } + } + + } catch (Exception e) { + log.error("更新批次数据到目标系统失败,批次ID: {}", id, e); + result.put("success", false); + result.put("message", "更新批次数据到目标系统失败: " + e.getMessage()); + } + + return result; + } } diff --git a/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/service/impl/DataiIntegrationObjectServiceImpl.java b/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/service/impl/DataiIntegrationObjectServiceImpl.java index 6fb24a2e..c0859909 100644 --- a/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/service/impl/DataiIntegrationObjectServiceImpl.java +++ b/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/service/impl/DataiIntegrationObjectServiceImpl.java @@ -1336,4 +1336,17 @@ public class DataiIntegrationObjectServiceImpl implements IDataiIntegrationObjec return realtimeSyncObjects; } + + @Override + public Map pushSingleObjectDataToTarget(Integer id, String targetOrgType) { + + + return null; + } + + @Override + public Map pushMultipleObjectDataToTarget(Integer[] ids, String targetOrgType) { + + return null; + } } diff --git a/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/resources/mapper/integration/DataiIntegrationBatchMapper.xml b/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/resources/mapper/integration/DataiIntegrationBatchMapper.xml index e5d6318a..b5147050 100644 --- a/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/resources/mapper/integration/DataiIntegrationBatchMapper.xml +++ b/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/resources/mapper/integration/DataiIntegrationBatchMapper.xml @@ -11,6 +11,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + + @@ -18,6 +20,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + + @@ -33,6 +37,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" dib.label, dib.sf_num, dib.db_num, + dib.sf_insert_num, + dib.sf_update_num, dib.sync_type, dib.batch_field, dib.sync_status, @@ -40,6 +46,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" dib.sync_end_date, dib.first_sync_time, dib.last_sync_time, + dib.first_push_time, + dib.last_push_time, dib.remark, dib.create_by, dib.create_time, @@ -79,6 +87,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" label, sf_num, db_num, + sf_insert_num, + sf_update_num, sync_type, batch_field, sync_status, @@ -86,6 +96,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" sync_end_date, first_sync_time, last_sync_time, + first_push_time, + last_push_time, remark, create_by, create_time, @@ -98,6 +110,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" #{label}, #{sfNum}, #{dbNum}, + #{sfInsertNum}, + #{sfUpdateNum}, #{syncType}, #{batchField}, #{syncStatus}, @@ -105,6 +119,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" #{syncEndDate}, #{firstSyncTime}, #{lastSyncTime}, + #{firstPushTime}, + #{lastPushTime}, #{remark}, #{createBy}, #{createTime}, @@ -121,6 +137,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" label = #{label}, sf_num = #{sfNum}, db_num = #{dbNum}, + sf_insert_num = #{sfInsertNum}, + sf_update_num = #{sfUpdateNum}, sync_type = #{syncType}, batch_field = #{batchField}, sync_status = #{syncStatus}, @@ -128,6 +146,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" sync_end_date = #{syncEndDate}, first_sync_time = #{firstSyncTime}, last_sync_time = #{lastSyncTime}, + first_push_time = #{firstPushTime}, + last_push_time = #{lastPushTime}, remark = #{remark}, create_by = #{createBy}, create_time = #{createTime}, diff --git a/datai-scenes/datai-scene-salesforce/docs/index.md b/datai-scenes/datai-scene-salesforce/docs/index.md index f34f848b..5049aeeb 100644 --- a/datai-scenes/datai-scene-salesforce/docs/index.md +++ b/datai-scenes/datai-scene-salesforce/docs/index.md @@ -45,6 +45,7 @@ - [03创建工作流提示词.md](prompts/03创建工作流提示词.md) - [04工作流整合白板提示词.md](prompts/04工作流整合白板提示词.md) - [05创建项目规则提示词.md](prompts/05创建项目规则提示词.md) + - [06更新当前项目README说明文档.md](prompts/06更新当前项目README说明文档.md) - 更新项目README文档的提示词 ### 6. 会话记录 @@ -137,4 +138,4 @@ ## 最后更新 - 更新时间: 2026-01-14 -- 更新内容: 添加 DataiConfigurationController 接口文档,包含查询配置列表、导出配置列表、获取配置详细信息、新增配置、修改配置、删除配置、刷新配置缓存、查询配置缓存状态八个接口的详细文档 +- 更新内容: 添加 06更新当前项目README说明文档.md 提示词文档,用于指导 AI 根据项目实际情况更新 README 文档 diff --git a/datai-scenes/datai-scene-salesforce/docs/prompts/06更新项目说明文档提示词.md b/datai-scenes/datai-scene-salesforce/docs/prompts/06更新项目说明文档提示词.md new file mode 100644 index 00000000..dd72fd09 --- /dev/null +++ b/datai-scenes/datai-scene-salesforce/docs/prompts/06更新项目说明文档提示词.md @@ -0,0 +1,25 @@ +角色设定:你是一位资深的 Java 架构师,擅长编写清晰、专业的项目文档。 + +任务目标:请深度扫描我提供的代码,并根据代码现状全面完善 README.md 文件。 + +扫描要求: + +技术栈识别:自动识别并列出项目使用的 Java 版本、框架(如 Spring Boot)、构建工具(Maven/Gradle)及关键依赖。 + +核心模块说明:分析包结构,解释主要业务模块的功能。 + +配置说明:扫描 application.yml 或 properties 文件,提取关键配置项说明。 + +README 结构规范: + +Project Title & Description: 简洁明了的项目名称和功能概述。 + +Tech Stack: 采用表格或列表形式展示。 + +Project Structure: 展示核心目录树并注释关键文件夹。 + +Getting Started: 包含环境要求、编译指令(如 ./mvnw clean install)和启动方式。 + +Key Features: 基于代码逻辑总结出的核心功能点。 + +执行指令:请先输出一份你观察到的项目概览,在我确认后,直接修改并覆盖当前的 README.md 文件 \ No newline at end of file diff --git a/docs/design/0001-statistics-optimization.md b/docs/design/0001-statistics-optimization.md deleted file mode 100644 index 42c5ed54..00000000 --- a/docs/design/0001-statistics-optimization.md +++ /dev/null @@ -1,134 +0,0 @@ -# 变更统计接口优化设计 - -## 1. 性能优化 - 索引建议 - -### 1.1 当前查询分析 - -`selectChangeStatistics` 方法使用以下字段进行过滤: - -- `change_type` - 变更类型 -- `operation_type` - 操作类型 -- `object_api` - 对象 API 名称 -- `sync_status` - 同步状态 -- `is_custom` - 是否自定义 -- `change_time` - 变更时间(用于时间范围过滤) - -### 1.2 索引建议 - -建议为以下字段添加索引: - -1. **单列索引**: - - `change_time` - 用于时间范围查询,这是最频繁的过滤条件之一 - - `sync_status` - 用于同步状态过滤 - - `change_type` - 用于变更类型过滤 - - `operation_type` - 用于操作类型过滤 - -2. **复合索引**: - - `(change_type, operation_type)` - 经常一起使用的过滤条件 - - `(object_api, change_type)` - 用于特定对象的变更查询 - - `(change_time, sync_status)` - 用于时间范围加同步状态的组合查询 - -### 1.3 索引创建语句 - -```sql --- 单列索引 -CREATE INDEX idx_metadata_change_time ON datai_integration_metadata_change(change_time); -CREATE INDEX idx_metadata_sync_status ON datai_integration_metadata_change(sync_status); -CREATE INDEX idx_metadata_change_type ON datai_integration_metadata_change(change_type); -CREATE INDEX idx_metadata_operation_type ON datai_integration_metadata_change(operation_type); - --- 复合索引 -CREATE INDEX idx_metadata_change_operation ON datai_integration_metadata_change(change_type, operation_type); -CREATE INDEX idx_metadata_object_change ON datai_integration_metadata_change(object_api, change_type); -CREATE INDEX idx_metadata_time_status ON datai_integration_metadata_change(change_time, sync_status); -``` - -## 2. 功能增强 - -### 2.1 缓存机制 - -为统计结果添加缓存,减少频繁查询的开销: - -- 使用 Redis 缓存统计结果 -- 缓存键包含查询参数的哈希值 -- 设置合理的过期时间(如 5 分钟) -- 提供缓存刷新接口 - -### 2.2 分组统计 - -支持按不同维度分组统计: - -- 按变更类型分组 -- 按操作类型分组 -- 按对象 API 分组 -- 按同步状态分组 - -### 2.3 趋势分析 - -支持按时间维度分析变更趋势: - -- 按日统计 -- 按周统计 -- 按月统计 -- 按季度统计 - -## 3. 代码优化 - -### 3.1 参数验证 - -增强参数验证,确保: - -- 时间格式正确 -- 参数值有效 -- 防止 SQL 注入 - -### 3.2 异常处理 - -添加详细的异常处理和日志记录: - -- 捕获并记录 SQL 异常 -- 记录查询参数和执行时间 -- 提供友好的错误信息 - -### 3.3 代码结构 - -提取统计逻辑为单独的方法,提高代码复用性: - -- 分离查询构建和结果处理 -- 封装统计计算逻辑 -- 统一响应格式处理 - -## 4. 响应格式优化 - -确保响应格式满足前端展示需求: - -- 标准化错误响应 -- 统一数据结构 -- 提供详细的统计项说明 - -## 5. 测试策略 - -验证优化效果的测试策略: - -- 性能测试:测试不同数据量下的查询性能 -- 功能测试:验证所有统计维度和过滤条件 -- 边界测试:测试极端情况下的行为 -- 回归测试:确保现有功能不受影响 - -## 6. 实施计划 - -1. 添加索引优化 SQL 查询性能 -2. 实现缓存机制减少查询开销 -3. 添加分组统计和趋势分析功能 -4. 增强参数验证和异常处理 -5. 优化代码结构和响应格式 -6. 运行测试验证优化效果 -7. 部署到生产环境 - -## 7. 预期效果 - -- 查询性能提升 50% 以上 -- 响应时间减少到 500ms 以内 -- 支持更大数据量的统计查询 -- 功能更全面,满足更多业务场景 -- 代码更健壮,易于维护和扩展 \ No newline at end of file