feat: 优化 README 文档并完善批次数据管理功能

主要修改:

1. README.md 优化
   - 明确项目当前实现状态(从 Salesforce 同步数据到本地)
   - 添加开发计划,包含 4 个主要方向:
     * 数据推送到目标 Salesforce 系统
     * 源 Salesforce 系统文件下载
     * User 权限同步集成系统
     * 集成可视化报表插件

2. 批次数据管理功能完善
   - DataiIntegrationBatchController: 新增 insertBatchDataToTarget 和 updateBatchDataToTarget 方法
   - IDataiIntegrationBatchService: 添加批次数据插入和更新接口
   - DataiIntegrationBatchServiceImpl: 实现批次数据插入和更新逻辑
   - DataiIntegrationBatch: 添加 new_id、is_insert、is_update 字段
   - DataiIntegrationBatchDto/DataiIntegrationBatchVo: 同步字段定义
   - DataiIntegrationBatchMapper.xml: 添加相关 SQL 映射

3. 文档更新
   - 创建提示词文档:06更新项目说明文档提示词.md
   - 更新 Salesforce 模块文档索引
   - 删除过时文档

4. 配置更新
   - 更新 .gitignore 配置
This commit is contained in:
Kris 2026-01-14 17:24:57 +08:00
parent a383cde260
commit 7459f8ab53
15 changed files with 1029 additions and 341 deletions

1
.gitignore vendored
View File

@ -52,3 +52,4 @@ nbdist/
effective-pom.xml effective-pom.xml
/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/resources/参考代码/ /datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/resources/参考代码/
/datai-scenes/datai-scene-salesforce/docs/sql/

465
README.md
View File

@ -1,222 +1,277 @@
<p align="center"> # Datai - Salesforce 集成系统
<span>
<img alt="logo" src="https://oscimg.oschina.net/oscnet/up-d3d0a9303e11d522a06cd263f3079027715.png">
</span>
<span>+</span>
<span>
<img alt="logo" src="./doc/image/logo.png">
</span>
</p>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">datai v3.8.9-G</h1>
<h4 align="center">基于SpringBoot3+Vue3前后端分离的Java快速开发框架</h4>
<p align="center">
<img src="https://img.shields.io/github/license/mashape/apistatus.svg">
</p>
# 当前版本是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) **当前实现状态**:项目目前专注于从 Salesforce 源系统同步数据到本地数据库,包括元数据管理、批量数据同步、实时数据同步等功能。
2. [DataI-Vue3-Geek: 这是集成系统生态的Vue3版本 (gitee.com)](https://gitee.com/geek-xd/ruo-yi-vue3-geek)
与本项目同为一个作者开发,兼容性最好,学习成本最低。 ## 技术栈
# 引言 | 技术 | 版本 | 说明 |
|------|------|------|
| Java | 21 / 22 | 根项目使用 Java 21Salesforce 模块使用 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中我们深入调研了企业开发中常用的DataI扩展并直接在框架中集成确保开发者能够快速上手高效开发。同时我们采用了最新的SpringBoot3+Vue3技术栈彻底移除了为了兼容Java1.8而保留的老旧方法。更为重要的是我们为TypeScript开发环境加入了常用的tsconfig.json配置使得开发者在VSCode等编辑器中能够获得更为舒适、便捷的语法提示体验。 ├── datai-admin/ # 管理后台启动模块
├── datai-framework/ # 框架核心模块
datai不仅仅是一个简单的升级版本更是对于DataI生态的一次全面优化与整合。我们相信通过datai开发者将能够更为高效、愉悦地开发出优秀的企业级应用。 ├── datai-system/ # 系统管理模块
├── datai-common/ # 通用工具模块
## 平台简介 ├── datai-scenes/ # 场景模块
│ ├── datai-scene-salesforce/ # Salesforce 集成场景
若依是一套全部开源的快速开发平台,毫无保留给个人及企业免费使用。 │ │ ├── datai-salesforce-auth/ # 认证相关模块
│ │ ├── datai-salesforce-common/ # 通用工具和常量
* 前端采用Vue3、Element Plus。 │ │ ├── datai-salesforce-integration/ # 核心集成模块
* 后端采用Spring Boot3、Spring Security、Redis & Jwt。 │ │ ├── datai-salesforce-setting/ # 配置管理模块
* 权限认证使用Jwt支持多终端认证系统。 │ │ └── datai-salesforce-starter/ # 启动器模块
* 支持加载动态权限菜单,多方式轻松权限控制。 │ ├── datai-scene-auth/ # 认证场景
* 高效率开发,使用代码生成器可以一键生成前后端代码。 │ │ ├── datai-auth-common/ # 认证通用模块
* 多数据源与分库分表默认集成 │ │ ├── datai-auth-starter/ # 认证启动器
* 所有非基本模块可随意插拔,让开发更加简单高效 │ │ ├── datai-oauth-justauth/ # JustAuth OAuth
* 提供了多个工具模块助力开发在线接口模块、mybatis-jpa模块 │ │ ├── datai-oauth-wx/ # 微信 OAuth
* 提供了多个常见业务模块简化开发,如:第三方认证模块、支付模块 │ │ ├── datai-tfa-email/ # 邮箱双因子认证
* 提供了多个常见的服务模块集成开发websocket模块、minio模块 │ │ └── datai-tfa-phone/ # 手机双因子认证
* 特别鸣谢:[element](https://github.com/ElemeFE/element)[vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)[eladmin-web](https://github.com/elunez/eladmin-web)。 │ ├── datai-scene-pay/ # 支付场景
* 阿里云折扣场:[点我进入](http://aly.datai.vip),腾讯云秒杀场:[点我进入](http://txy.datai.vip)&nbsp;&nbsp; │ │ ├── datai-pay-common/ # 支付通用模块
* 阿里云优惠券:[点我领取](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)&nbsp;&nbsp; │ │ ├── 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 文件存储
1. 改用SpringBoot3+java17的更新的技术栈并改掉所有的弃用的方法全面拥抱java17的全新特性。 ├── datai-plugins/ # 插件模块
2. 升级了代码生成器配合本项目的vue3版本才可用使关联表生成更加简单 │ ├── datai-plugins-starter/ # 插件启动器
3. 改用最新版本的SpringSecurity安全框架以及采用最新的lambda 表达式的配置方式,更加通俗易懂! │ ├── datai-mybatis-plus/ # MyBatis Plus 插件
4. 自动Api文档以springfox替代springdoc来适配knife4j框架的4.x版本更好的适配springboot3 │ ├── datai-cache-redis/ # Redis 缓存插件
5. 默认引入mybatis-plus增强mybatis并自创工具模块mybatis-jpa简化CRUD │ ├── datai-cache-ehcache/ # Ehcache 缓存插件
6. 默认引入lombok简化代码基础模块并未使用mybatis-plus和lombok对这两个扩展有争议的小伙伴可以直接删除不会影响到框架本身滴以及knife4j直接删除也不会影响到springdoc主要还是为了方便咱们开发者呢 │ ├── datai-rabbitmq/ # RabbitMQ 消息队列插件
7. 提供了大量可随意插拔的模块助力快速开发同时datai插件集成中的常用插件也以模块的形式直接集成在了项目中也是可以随意插拔的嗷~ │ ├── datai-websocket/ # WebSocket 插件
│ ├── datai-netty/ # Netty 网络插件
## 模块介绍(简单开发必看) │ └── datai-atomikos/ # Atomikos 分布式事务插件
└── datai-models/ # 模型模块
* 最简单的开发就是删除所有的可移除模块,按需添加模块。 ├── datai-models-starter/ # 模型启动器
* 测试中的模块请自己使用的时候一定要测试一下。 ├── datai-quartz/ # Quartz 定时任务
* 对于小白,开发中的模块请直接删除。 ├── datai-form/ # 表单管理
├── datai-message/ # 消息管理
```plaintext ├── datai-generator/ # 代码生成器
com.datai ├── datai-online/ # 在线用户
├── datai-admin // 后台服务 └── datai-flowable/ # 工作流
├── 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 // 系统代码
``` ```
## 内置功能 ## 核心功能
1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。 ### Salesforce 集成模块(当前实现)
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 同步数据到本地数据库。
### 新加功能和增强功能演示 #### 认证管理
- **多种登录方式**: OAuth2、SessionId、CLI、Legacy Credential
- **会话管理**: 登录会话的创建、查询和销毁
- **历史记录**: 登录历史记录的查询和统计
<table> #### 数据同步(源系统 → 本地)
<tr> - **Bulk API v1/v2**: 批量数据同步,支持大规模数据导入导出
<td><img src="./doc/image/message.png"/></td> - **Partner API**: SOAP API 数据交互,支持 CRUD 操作
<td><img src="./doc/image/flowable.png"/></td> - **REST API**: RESTful API 数据交互
</tr> - **CDC 实时同步**: Change Data Capture 实时数据同步
<tr>
<td><img src="./doc/image/online-mb-list.png"/></td>
<td><img src="./doc/image/online-mb-edit.png"/></td>
</tr>
<tr>
<td><img src="./doc/image/online-mb-code.png"/></td>
<td><img src="./doc/image/form-edit.png"/></td>
</tr>
<tr>
<td><img src="./doc/image/code-edit.png"/></td>
<td><img src="./doc/image/code-show.png"/></td>
</tr>
</table>
### 原有功能演示 #### 元数据管理
- **对象管理**: Salesforce 对象的查询、同步和结构创建
- **字段管理**: 对象字段的查询和管理
- **Picklist 管理**: Picklist 值的查询和管理
- **Lookup 过滤器**: Lookup 过滤器的查询和管理
<table> #### 批次管理
<tr> - **批次创建**: 创建数据同步批次
<td><img src="https://oscimg.oschina.net/oscnet/cd1f90be5f2684f4560c9519c0f2a232ee8.jpg"/></td> - **批次数据插入**: 将本地数据插入到 Salesforce保存返回 ID
<td><img src="https://oscimg.oschina.net/oscnet/1cbcf0e6f257c7d3a063c0e3f2ff989e4b3.jpg"/></td> - **批次数据更新**: 使用保存的 ID 更新 Salesforce 数据
</tr> - **批次重试**: 失败批次的重试机制
<tr> - **批次历史**: 批次操作历史记录
<td><img src="https://oscimg.oschina.net/oscnet/up-8074972883b5ba0622e13246738ebba237a.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-9f88719cdfca9af2e58b352a20e23d43b12.png"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-39bf2584ec3a529b0d5a3b70d15c9b37646.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-936ec82d1f4872e1bc980927654b6007307.png"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-b2d62ceb95d2dd9b3fbe157bb70d26001e9.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-d67451d308b7a79ad6819723396f7c3d77a.png"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/5e8c387724954459291aafd5eb52b456f53.jpg"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/644e78da53c2e92a95dfda4f76e6d117c4b.jpg"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-8370a0d02977eebf6dbf854c8450293c937.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-49003ed83f60f633e7153609a53a2b644f7.png"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-d4fe726319ece268d4746602c39cffc0621.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-c195234bbcd30be6927f037a6755e6ab69c.png"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/b6115bc8c31de52951982e509930b20684a.jpg"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-5e4daac0bb59612c5038448acbcef235e3a.png"/></td>
</tr>
</table>
# 联系我们: #### 实时同步
- **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)

View File

@ -28,6 +28,7 @@ import com.datai.common.utils.poi.ExcelUtil;
import com.datai.common.core.page.TableDataInfo; import com.datai.common.core.page.TableDataInfo;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import lombok.extern.slf4j.Slf4j;
/** /**
* 数据批次Controller * 数据批次Controller
@ -38,6 +39,7 @@ import io.swagger.v3.oas.annotations.Operation;
@RestController @RestController
@RequestMapping("/integration/batch") @RequestMapping("/integration/batch")
@Tag(name = "【数据批次】管理") @Tag(name = "【数据批次】管理")
@Slf4j
public class DataiIntegrationBatchController extends BaseController public class DataiIntegrationBatchController extends BaseController
{ {
@Autowired @Autowired
@ -180,4 +182,104 @@ public class DataiIntegrationBatchController extends BaseController
return error((String) result.get("message")); 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<String, Object> 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<String, Object> 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());
}
}
} }

View File

@ -318,4 +318,79 @@ public class DataiIntegrationObjectController extends BaseController
return error("同步多个对象数据时发生异常: " + e.getMessage()); 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<String, Object> 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<String, Object> 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());
}
}
} }

View File

@ -9,7 +9,7 @@ import com.datai.common.annotation.Excel;
import com.datai.common.core.domain.BaseEntity; import com.datai.common.core.domain.BaseEntity;
/** /**
* 数据批次对象 datai_integration_batch * 数据批次对象 datai_integr ation_batch
* *
* 字段更新规则 * 字段更新规则
* 1. 创建批次时设置的字段后续不允许修改 * 1. 创建批次时设置的字段后续不允许修改
@ -30,6 +30,12 @@ import com.datai.common.core.domain.BaseEntity;
* - lastSyncTime: 最后同步时间每次同步时更新 * - lastSyncTime: 最后同步时间每次同步时更新
* - updateTime: 更新时间每次更新时自动设置 * - updateTime: 更新时间每次更新时自动设置
* *
* 3. 推送时更新的字段
* - sfInsertNum: SF插入数据量每次推送时更新
* - sfUpdateNum: SF更新数据量每次推送时更新
* - firstPushTime: 首次推送时间仅在首次推送时设置
* - lastPushTime: 最后推送时间每次推送时更新
*
* @author datai * @author datai
* @date 2026-01-01 * @date 2026-01-01
*/ */
@ -67,6 +73,16 @@ public class DataiIntegrationBatch extends BaseEntity
@Excel(name = "本地数据量") @Excel(name = "本地数据量")
private Integer dbNum; 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 = "同步类型") @Schema(title = "同步类型")
@Excel(name = "同步类型") @Excel(name = "同步类型")
@ -101,6 +117,16 @@ public class DataiIntegrationBatch extends BaseEntity
@Schema(title = "最后同步时间") @Schema(title = "最后同步时间")
@Excel(name = "最后同步时间") @Excel(name = "最后同步时间")
private LocalDateTime lastSyncTime; private LocalDateTime lastSyncTime;
/** 首次推送时间 */
@Schema(title = "首次推送时间")
@Excel(name = "首次推送时间")
private LocalDateTime firstPushTime;
/** 最后推送时间 */
@Schema(title = "最后推送时间")
@Excel(name = "最后推送时间")
private LocalDateTime lastPushTime;
public void setId(Integer id) public void setId(Integer id)
{ {
this.id = 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) public void setSyncType(String syncType)
{ {
this.syncType = 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 @Override
public String toString() { public String toString() {
@ -254,6 +324,8 @@ public class DataiIntegrationBatch extends BaseEntity
.append("label", getLabel()) .append("label", getLabel())
.append("sfNum", getSfNum()) .append("sfNum", getSfNum())
.append("dbNum", getDbNum()) .append("dbNum", getDbNum())
.append("sfInsertNum", getSfInsertNum())
.append("sfUpdateNum", getSfUpdateNum())
.append("syncType", getSyncType()) .append("syncType", getSyncType())
.append("batchField", getBatchField()) .append("batchField", getBatchField())
.append("syncStatus", getSyncStatus()) .append("syncStatus", getSyncStatus())
@ -261,6 +333,8 @@ public class DataiIntegrationBatch extends BaseEntity
.append("syncEndDate", getSyncEndDate()) .append("syncEndDate", getSyncEndDate())
.append("firstSyncTime", getFirstSyncTime()) .append("firstSyncTime", getFirstSyncTime())
.append("lastSyncTime", getLastSyncTime()) .append("lastSyncTime", getLastSyncTime())
.append("firstPushTime", getFirstPushTime())
.append("lastPushTime", getLastPushTime())
.append("remark", getRemark()) .append("remark", getRemark())
.append("createBy", getCreateBy()) .append("createBy", getCreateBy())
.append("createTime", getCreateTime()) .append("createTime", getCreateTime())

View File

@ -42,6 +42,12 @@ public class DataiIntegrationBatchDto implements Serializable
/** 本地数据量 */ /** 本地数据量 */
private Integer dbNum; private Integer dbNum;
/** SF插入数据量 */
private Integer sfInsertNum;
/** SF更新数据量 */
private Integer sfUpdateNum;
/** 同步类型 */ /** 同步类型 */
private String syncType; private String syncType;
@ -63,6 +69,12 @@ public class DataiIntegrationBatchDto implements Serializable
/** 最后同步时间 */ /** 最后同步时间 */
private LocalDateTime lastSyncTime; private LocalDateTime lastSyncTime;
/** 首次推送时间 */
private LocalDateTime firstPushTime;
/** 最后推送时间 */
private LocalDateTime lastPushTime;
/** 备注 */ /** 备注 */
private String remark; private String remark;

View File

@ -37,6 +37,12 @@ public class DataiIntegrationBatchVo implements Serializable {
/** 本地数据量 */ /** 本地数据量 */
private Integer dbNum; private Integer dbNum;
/** SF插入数据量 */
private Integer sfInsertNum;
/** SF更新数据量 */
private Integer sfUpdateNum;
/** 同步类型 */ /** 同步类型 */
private String syncType; private String syncType;
@ -58,6 +64,12 @@ public class DataiIntegrationBatchVo implements Serializable {
/** 最后同步时间 */ /** 最后同步时间 */
private LocalDateTime lastSyncTime; private LocalDateTime lastSyncTime;
/** 首次推送时间 */
private LocalDateTime firstPushTime;
/** 最后推送时间 */
private LocalDateTime lastPushTime;
/** 备注 */ /** 备注 */
private String remark; private String remark;

View File

@ -90,4 +90,22 @@ public interface IDataiIntegrationBatchService
* @return 所有批次统计信息 * @return 所有批次统计信息
*/ */
public Map<String, Object> getAllBatchStatistics(); public Map<String, Object> getAllBatchStatistics();
/**
* 批次数据插入目标系统
*
* @param id 批次ID
* @param targetOrgType 目标ORG类型
* @return 插入结果
*/
public Map<String, Object> insertBatchDataToTarget(Integer id, String targetOrgType);
/**
* 批次数据更新目标系统
*
* @param id 批次ID
* @param targetOrgType 目标ORG类型
* @return 更新结果
*/
public Map<String, Object> updateBatchDataToTarget(Integer id, String targetOrgType);
} }

View File

@ -136,5 +136,23 @@ public interface IDataiIntegrationObjectService
*/ */
public Map<String, Object> syncMultipleObjectData(Integer[] ids); public Map<String, Object> syncMultipleObjectData(Integer[] ids);
/**
* 推送单对象数据到目标系统
*
* @param id 对象ID
* @param targetOrgType 目标ORG类型
* @return 推送结果
*/
public Map<String, Object> pushSingleObjectDataToTarget(Integer id, String targetOrgType);
/**
* 推送多个对象数据到目标系统
*
* @param ids 对象ID集合
* @param targetOrgType 目标ORG类型
* @return 推送结果
*/
public Map<String, Object> pushMultipleObjectDataToTarget(Integer[] ids, String targetOrgType);
Set<DataiIntegrationObject> getRealtimeSyncObjects(); Set<DataiIntegrationObject> getRealtimeSyncObjects();
} }

View File

@ -993,4 +993,400 @@ public class DataiIntegrationBatchServiceImpl implements IDataiIntegrationBatchS
return statistics; return statistics;
} }
@Override
public Map<String, Object> insertBatchDataToTarget(Integer id, String targetOrgType) {
Map<String, Object> 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<Map<String, Object>> 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<Map<String, Object>> failedRecords = new ArrayList<>();
for (int i = 0; i < dataList.size(); i++) {
Map<String, Object> dataMap = dataList.get(i);
try {
String localId = (String) dataMap.get("id");
if (localId == null || localId.isEmpty()) {
log.warn("记录ID为空跳过该记录索引: {}", i);
failureCount++;
Map<String, Object> failedRecord = new HashMap<>();
failedRecord.put("index", i);
failedRecord.put("reason", "记录ID为空");
failedRecords.add(failedRecord);
continue;
}
Map<String, Object> 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<String, Object> 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<Map<String, Object>> updateMaps = new ArrayList<>();
Map<String, Object> newIdMap = new HashMap<>();
newIdMap.put("key", "new_id");
newIdMap.put("value", newId);
updateMaps.add(newIdMap);
Map<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> updateBatchDataToTarget(Integer id, String targetOrgType) {
Map<String, Object> 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<Map<String, Object>> 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<Map<String, Object>> failedRecords = new ArrayList<>();
for (int i = 0; i < dataList.size(); i++) {
Map<String, Object> 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<String, Object> 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<String, Object> failedRecord = new HashMap<>();
failedRecord.put("index", i);
failedRecord.put("localId", localId);
failedRecord.put("reason", "new_id为空请先执行插入操作");
failedRecords.add(failedRecord);
continue;
}
Map<String, Object> 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<Map<String, Object>> updateMaps = new ArrayList<>();
Map<String, Object> 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<String, Object> 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;
}
} }

View File

@ -1336,4 +1336,17 @@ public class DataiIntegrationObjectServiceImpl implements IDataiIntegrationObjec
return realtimeSyncObjects; return realtimeSyncObjects;
} }
@Override
public Map<String, Object> pushSingleObjectDataToTarget(Integer id, String targetOrgType) {
return null;
}
@Override
public Map<String, Object> pushMultipleObjectDataToTarget(Integer[] ids, String targetOrgType) {
return null;
}
} }

View File

@ -11,6 +11,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="label" column="label" /> <result property="label" column="label" />
<result property="sfNum" column="sf_num" /> <result property="sfNum" column="sf_num" />
<result property="dbNum" column="db_num" /> <result property="dbNum" column="db_num" />
<result property="sfInsertNum" column="sf_insert_num" />
<result property="sfUpdateNum" column="sf_update_num" />
<result property="syncType" column="sync_type" /> <result property="syncType" column="sync_type" />
<result property="batchField" column="batch_field" /> <result property="batchField" column="batch_field" />
<result property="syncStatus" column="sync_status" /> <result property="syncStatus" column="sync_status" />
@ -18,6 +20,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="syncEndDate" column="sync_end_date" /> <result property="syncEndDate" column="sync_end_date" />
<result property="firstSyncTime" column="first_sync_time" /> <result property="firstSyncTime" column="first_sync_time" />
<result property="lastSyncTime" column="last_sync_time" /> <result property="lastSyncTime" column="last_sync_time" />
<result property="firstPushTime" column="first_push_time" />
<result property="lastPushTime" column="last_push_time" />
<result property="remark" column="remark" /> <result property="remark" column="remark" />
<result property="createBy" column="create_by" /> <result property="createBy" column="create_by" />
<result property="createTime" column="create_time" /> <result property="createTime" column="create_time" />
@ -33,6 +37,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
dib.label, dib.label,
dib.sf_num, dib.sf_num,
dib.db_num, dib.db_num,
dib.sf_insert_num,
dib.sf_update_num,
dib.sync_type, dib.sync_type,
dib.batch_field, dib.batch_field,
dib.sync_status, dib.sync_status,
@ -40,6 +46,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
dib.sync_end_date, dib.sync_end_date,
dib.first_sync_time, dib.first_sync_time,
dib.last_sync_time, dib.last_sync_time,
dib.first_push_time,
dib.last_push_time,
dib.remark, dib.remark,
dib.create_by, dib.create_by,
dib.create_time, dib.create_time,
@ -79,6 +87,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="label != null">label,</if> <if test="label != null">label,</if>
<if test="sfNum != null">sf_num,</if> <if test="sfNum != null">sf_num,</if>
<if test="dbNum != null">db_num,</if> <if test="dbNum != null">db_num,</if>
<if test="sfInsertNum != null">sf_insert_num,</if>
<if test="sfUpdateNum != null">sf_update_num,</if>
<if test="syncType != null">sync_type,</if> <if test="syncType != null">sync_type,</if>
<if test="batchField != null">batch_field,</if> <if test="batchField != null">batch_field,</if>
<if test="syncStatus != null">sync_status,</if> <if test="syncStatus != null">sync_status,</if>
@ -86,6 +96,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="syncEndDate != null">sync_end_date,</if> <if test="syncEndDate != null">sync_end_date,</if>
<if test="firstSyncTime != null">first_sync_time,</if> <if test="firstSyncTime != null">first_sync_time,</if>
<if test="lastSyncTime != null">last_sync_time,</if> <if test="lastSyncTime != null">last_sync_time,</if>
<if test="firstPushTime != null">first_push_time,</if>
<if test="lastPushTime != null">last_push_time,</if>
<if test="remark != null">remark,</if> <if test="remark != null">remark,</if>
<if test="createBy != null">create_by,</if> <if test="createBy != null">create_by,</if>
<if test="createTime != null">create_time,</if> <if test="createTime != null">create_time,</if>
@ -98,6 +110,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="label != null">#{label},</if> <if test="label != null">#{label},</if>
<if test="sfNum != null">#{sfNum},</if> <if test="sfNum != null">#{sfNum},</if>
<if test="dbNum != null">#{dbNum},</if> <if test="dbNum != null">#{dbNum},</if>
<if test="sfInsertNum != null">#{sfInsertNum},</if>
<if test="sfUpdateNum != null">#{sfUpdateNum},</if>
<if test="syncType != null">#{syncType},</if> <if test="syncType != null">#{syncType},</if>
<if test="batchField != null">#{batchField},</if> <if test="batchField != null">#{batchField},</if>
<if test="syncStatus != null">#{syncStatus},</if> <if test="syncStatus != null">#{syncStatus},</if>
@ -105,6 +119,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="syncEndDate != null">#{syncEndDate},</if> <if test="syncEndDate != null">#{syncEndDate},</if>
<if test="firstSyncTime != null">#{firstSyncTime},</if> <if test="firstSyncTime != null">#{firstSyncTime},</if>
<if test="lastSyncTime != null">#{lastSyncTime},</if> <if test="lastSyncTime != null">#{lastSyncTime},</if>
<if test="firstPushTime != null">#{firstPushTime},</if>
<if test="lastPushTime != null">#{lastPushTime},</if>
<if test="remark != null">#{remark},</if> <if test="remark != null">#{remark},</if>
<if test="createBy != null">#{createBy},</if> <if test="createBy != null">#{createBy},</if>
<if test="createTime != null">#{createTime},</if> <if test="createTime != null">#{createTime},</if>
@ -121,6 +137,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="label != null">label = #{label},</if> <if test="label != null">label = #{label},</if>
<if test="sfNum != null">sf_num = #{sfNum},</if> <if test="sfNum != null">sf_num = #{sfNum},</if>
<if test="dbNum != null">db_num = #{dbNum},</if> <if test="dbNum != null">db_num = #{dbNum},</if>
<if test="sfInsertNum != null">sf_insert_num = #{sfInsertNum},</if>
<if test="sfUpdateNum != null">sf_update_num = #{sfUpdateNum},</if>
<if test="syncType != null">sync_type = #{syncType},</if> <if test="syncType != null">sync_type = #{syncType},</if>
<if test="batchField != null">batch_field = #{batchField},</if> <if test="batchField != null">batch_field = #{batchField},</if>
<if test="syncStatus != null">sync_status = #{syncStatus},</if> <if test="syncStatus != null">sync_status = #{syncStatus},</if>
@ -128,6 +146,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="syncEndDate != null">sync_end_date = #{syncEndDate},</if> <if test="syncEndDate != null">sync_end_date = #{syncEndDate},</if>
<if test="firstSyncTime != null">first_sync_time = #{firstSyncTime},</if> <if test="firstSyncTime != null">first_sync_time = #{firstSyncTime},</if>
<if test="lastSyncTime != null">last_sync_time = #{lastSyncTime},</if> <if test="lastSyncTime != null">last_sync_time = #{lastSyncTime},</if>
<if test="firstPushTime != null">first_push_time = #{firstPushTime},</if>
<if test="lastPushTime != null">last_push_time = #{lastPushTime},</if>
<if test="remark != null">remark = #{remark},</if> <if test="remark != null">remark = #{remark},</if>
<if test="createBy != null">create_by = #{createBy},</if> <if test="createBy != null">create_by = #{createBy},</if>
<if test="createTime != null">create_time = #{createTime},</if> <if test="createTime != null">create_time = #{createTime},</if>

View File

@ -45,6 +45,7 @@
- [03创建工作流提示词.md](prompts/03创建工作流提示词.md) - [03创建工作流提示词.md](prompts/03创建工作流提示词.md)
- [04工作流整合白板提示词.md](prompts/04工作流整合白板提示词.md) - [04工作流整合白板提示词.md](prompts/04工作流整合白板提示词.md)
- [05创建项目规则提示词.md](prompts/05创建项目规则提示词.md) - [05创建项目规则提示词.md](prompts/05创建项目规则提示词.md)
- [06更新当前项目README说明文档.md](prompts/06更新当前项目README说明文档.md) - 更新项目README文档的提示词
### 6. 会话记录 ### 6. 会话记录
@ -137,4 +138,4 @@
## 最后更新 ## 最后更新
- 更新时间: 2026-01-14 - 更新时间: 2026-01-14
- 更新内容: 添加 DataiConfigurationController 接口文档,包含查询配置列表、导出配置列表、获取配置详细信息、新增配置、修改配置、删除配置、刷新配置缓存、查询配置缓存状态八个接口的详细文档 - 更新内容: 添加 06更新当前项目README说明文档.md 提示词文档,用于指导 AI 根据项目实际情况更新 README 文档

View File

@ -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 文件

View File

@ -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 以内
- 支持更大数据量的统计查询
- 功能更全面,满足更多业务场景
- 代码更健壮,易于维护和扩展