编辑 | blame | 历史 | 原始文档

采购订单管理系统

项目概述

采购订单管理系统用于管理企业的采购流程,包括采购订单、送货通知、收货等功能。系统主要实现了ERP系统与SRM系统之间的采购数据比对功能,帮助企业及时发现并处理两个系统之间的数据差异。

技术架构

  • Spring Boot框架
  • MyBatis Plus ORM框架
  • Oracle数据库
  • 前后端分离架构

主要功能模块

  1. 采购订单管理
  2. 送货通知管理
  3. 收货管理
  4. 退货管理
  5. 供应商管理
  6. ERP与SRM系统数据比对
  7. 钉钉消息通知

核心数据实体

  • 采购订单明细(PurchaseOrderDetail): 存储SRM系统采购订单数据
  • ERP采购订单数据(MesRohInData): 存储ERP系统采购订单数据
  • 采购订单比对结果(PurchaseOrderCompare): 存储两系统数据比对结果

核心功能实现

数据同步与比对流程

  1. 从SRM系统API获取采购订单明细数据
  2. 根据单号和项次查询ERP系统中对应的采购订单数据
  3. 计算两个系统中的待收数量差异
  4. 将比对结果保存到数据库

使用注意事项

  • API调用频率不低于2小时
  • 请求时间范围不大于24小时
  • 数据类型转换需注意:ERP系统(Long)与SRM系统(Integer)

REST API接口

钉钉消息发送接口

接口信息

  • URL: POST /api/dingtalk/sendMessage
  • 功能: 发送钉钉消息通知
  • 描述: 根据检验单号发送不合格检验单的钉钉通知消息

请求参数

{
  "releaseNo": "检验单号"
}

参数说明:

  • releaseNo (String, 必填): 检验单号,用于查询对应的钉钉消息内容

响应结果

{
  "code": 200,
  "message": null,
  "successful": 0,
  "data": "接收成功"
}

响应字段说明:

  • code (Integer): 响应状态码,200表示成功,500表示失败
  • message (String): 错误信息,成功时为null
  • successful (Integer): 成功标识,0表示成功,1表示失败
  • data (String): 响应数据,成功时为"接收成功",失败时为"接收失败"

使用示例

请求示例:

curl -X POST http://localhost:9095/api/dingtalk/sendMessage \
  -H "Content-Type: application/json" \
  -d '{"releaseNo": "IQC202501270001"}'

成功响应示例:

{
  "code": 200,
  "message": null,
  "successful": 0,
  "data": "接收成功"
}

失败响应示例:

{
  "code": 500,
  "message": "检验单号不能为空",
  "successful": 1,
  "data": "接收失败"
}

错误处理

接口会处理以下错误情况:

  1. 参数为空: 检验单号为空或null时返回错误信息
  2. 数据不存在: 检验单号对应的钉钉消息内容不存在时返回失败
  3. 发送失败: 钉钉API调用失败时返回失败信息
  4. 系统异常: 其他系统异常时返回异常信息

注意事项

  1. 确保检验单号在DINGTALK_MSG表中有对应的记录
  2. 确保钉钉应用配置正确且有发送权限
  3. 接口调用频率建议不超过每分钟1次
  4. 建议在生产环境中添加接口访问权限控制

详细文档

更多详细的API使用说明、错误处理、最佳实践等内容,请参考 API_DOCUMENTATION.md 文件。

数据库表结构

项目中包含以下主要数据表:

  1. PURCHASE_ORDER_DETAIL - 存储SRM系统采购订单明细数据
  2. MES_ROH_IN_DATA - 存储ERP系统采购订单数据
  3. PURCHASE_ORDER_COMPARE - 存储ERP与SRM系统数据比对结果

表结构说明

采购订单明细表(PURCHASE_ORDER_DETAIL)的Oracle表结构

CREATE TABLE PURCHASE_ORDER_DETAIL
(
    ID                       NUMBER(19) PRIMARY KEY,
    PRODUCT_CODE             VARCHAR2(50),
    PRODUCT_NAME             VARCHAR2(100),
    PRODUCT_SCALE            VARCHAR2(200),
    INNER_VENDOR_CODE        VARCHAR2(50),
    INNER_VENDOR_NAME        VARCHAR2(100),
    PROFIT_CENTER_CODE       VARCHAR2(50),
    PROFIT_CENTER_NAME       VARCHAR2(100),
    PURCHASE_TYPE            NUMBER(2),
    PO_ERP_NO                VARCHAR2(50),
    LINE_NO                  VARCHAR2(50),
    PO_LINE_NO_SHOW          VARCHAR2(50),
    ERP_PURCHASE_DATE        NUMBER(19),
    ORDER_STATUS             VARCHAR2(2),
    PO_LINE_STATUS           NUMBER(2),
    PURCHASE_UNIT_CODE       VARCHAR2(20),
    PURCHASE_UNIT_NAME       VARCHAR2(20),
    TOTAL_ANSWER_QTY         NUMBER(10),
    TOTAL_DELIVERY_QTY       NUMBER(10),
    TOTAL_RECEIVE_QTY        NUMBER(10),
    TOTAL_RETURN_QTY         NUMBER(10),
    PO_WAIT_DELIVERY_QTY     NUMBER(10),
    SYS_WAIT_DELIVERY_QTY    NUMBER(10),
    RETURN_WAIT_DELIVERY_QTY NUMBER(10),
    EXPECTED_DATE            NUMBER(19),
    NOTICE_QTY               NUMBER(10),
    NOTICE_UN_DELIVERY_QTY   NUMBER(10),
    TOTAL_REPORT_FINISH_QTY  NUMBER(10),
    ISSUED_SETS              NUMBER(10),
    RECEIVE_STATUS           NUMBER(2),
    VALID_FLAG               NUMBER(1),
    EXTEND_N01               VARCHAR2(200),
    EXTEND_N02               VARCHAR2(200),
    EXTEND_N03               VARCHAR2(200),
    EXTEND_N04               VARCHAR2(200),
    EXTEND_N05               VARCHAR2(200),
    EXTEND_N06               VARCHAR2(200),
    EXTEND_N07               VARCHAR2(200),
    EXTEND_N08               VARCHAR2(200),
    EXTEND_N09               VARCHAR2(200),
    EXTEND_N10               VARCHAR2(200),
    EXTEND_N11               VARCHAR2(200),
    EXTEND_N12               VARCHAR2(200)
);

采购订单比对表(PURCHASE_ORDER_COMPARE)的Oracle表结构

CREATE TABLE PURCHASE_ORDER_COMPARE
(
    ID                   NUMBER(19) PRIMARY KEY,
    BILL_NO              VARCHAR2(50),
    ORDER_LINE_ID        VARCHAR2(50),
    LINE_NO              VARCHAR2(50),
    ERP_PURCHASE_QTY     NUMBER(10),
    ERP_RECEIVED_QTY     NUMBER(10),
    ERP_WAIT_RECEIVE_QTY NUMBER(10),
    SRM_PURCHASE_QTY     NUMBER(10),
    SRM_RECEIVED_QTY     NUMBER(10),
    SRM_WAIT_RECEIVE_QTY NUMBER(10),
    DIFF_FLAG            NUMBER(1),
    DIFF_QTY             NUMBER(10),
    PRODUCT_CODE         VARCHAR2(50),
    PRODUCT_NAME         VARCHAR2(100),
    CREATE_TIME          DATE,
    UPDATE_TIME          DATE
);

序列

用于主键ID生成的序列:

CREATE SEQUENCE SEQ_PURCHASE_ORDER_DETAIL
    START WITH 1
    INCREMENT BY 1
    NOCACHE
    NOCYCLE;

CREATE SEQUENCE SEQ_PURCHASE_ORDER_COMPARE
    START WITH 1
    INCREMENT BY 1
    NOCACHE
    NOCYCLE;

项目维护

如需修改或添加字段,请在相应的实体类文件中进行修改,并保持与数据库表结构同步。

数据库维护

如需修改数据库表结构,请按照以下步骤:

  1. 修改实体类文件中的字段定义
  2. 修改对应的SQL文件中的表结构定义
  3. 执行SQL语句更新数据库表结构

性能优化

系统在处理大量数据时可能会遇到内存占用过高的问题,特别是在执行以下操作时:

  1. 采购订单数据同步(syncPurchaseOrderDetails方法)
  2. 送货单数据处理

已实施的优化措施

  1. 分批处理数据:将大量数据分成小批次进行处理,减少一次性内存占用
  2. 及时释放对象引用:处理完毕后将不再使用的对象引用设为null,帮助GC回收内存
  3. 优化SQL查询:避免一次性加载大量数据到内存

JVM调优建议

在启动应用时,可以通过以下JVM参数优化内存使用:

java -Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -jar XkyCollection.jar

参数说明:

  • -Xms512m:初始堆内存大小为512MB
  • -Xmx1024m:最大堆内存大小为1024MB(根据服务器实际可用内存调整)
  • -XX:MetaspaceSize=128m:初始元空间大小为128MB
  • -XX:MaxMetaspaceSize=256m:最大元空间大小为256MB
  • -XX:+UseG1GC:使用G1垃圾收集器,适合大内存应用
  • -XX:MaxGCPauseMillis=200:最大GC暂停时间目标为200毫秒

监控建议

  1. 使用JConsole或VisualVM等工具监控应用内存使用情况
  2. 关注GC日志,分析内存使用模式
  3. 在生产环境中,可以添加以下参数开启GC日志:
    -Xloggc:/path/to/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps

常见问题排查

内存溢出(OutOfMemoryError)

如果遇到内存溢出问题:

  1. 检查是否有大量数据一次性加载到内存
  2. 确认是否有内存泄漏(对象创建后未被释放)
  3. 增加JVM堆内存大小
  4. 考虑使用分页查询或流式处理大数据量

服务卡顿

如果服务出现卡顿:

  1. 检查是否有长时间运行的事务
  2. 确认数据库连接是否正常释放
  3. 查看GC日志,确认是否频繁发生Full GC
  4. 优化数据库查询,添加适当的索引

最佳实践

  1. 避免在高峰期执行大量数据同步操作
  2. 对于定时任务,选择在系统负载较低的时段执行
  3. 使用适当的批处理大小(建议100-500条记录)
  4. 定期清理不再需要的历史数据

异步任务处理

系统使用异步任务处理机制来执行耗时操作,避免阻塞主线程和定时任务调度线程。主要包括以下几个部分:

线程池配置

系统配置了两个专用线程池:

  1. 采购订单同步线程池 (purchaseTaskExecutor)

    • 核心线程数:1(确保同一时间只有一个采购同步任务在执行)
    • 最大线程数:2
    • 队列容量:5
    • 拒绝策略:CallerRunsPolicy(调用者线程执行)
  2. 通用异步任务线程池 (taskExecutor)

    • 核心线程数:5
    • 最大线程数:10
    • 队列容量:25
    • 拒绝策略:CallerRunsPolicy(调用者线程执行)

定时任务优化

所有定时任务都使用异步执行方式,防止互相阻塞:

  1. 采购订单同步任务:每天12:05执行一次,使用专用线程池
  2. 设备实时数据获取:每5分钟执行一次,使用通用线程池
  3. 补偿逻辑:每5分钟执行多次,使用通用线程池
  4. 钉钉数据同步:每53分钟执行一次,使用通用线程池

任务执行状态管理

使用 AtomicBoolean 标记任务执行状态,避免同一任务重复执行:

private final AtomicBoolean isRunning = new AtomicBoolean(false);

// 任务开始前检查
if(!isRunning.

compareAndSet(false,true)){
        log.

info("上一次任务还在执行中,跳过本次执行");
    return;
            }

// 任务结束后重置状态
            finally{
            isRunning.

set(false);
}

异步任务执行流程

  1. 定时器触发任务
  2. 检查任务是否已在运行,如已运行则跳过
  3. 将任务提交到相应的线程池异步执行
  4. 定时器立即返回,不等待任务完成
  5. 任务在线程池中执行完毕后重置状态标记

这种机制确保了即使某个任务执行时间较长,也不会影响其他定时任务的正常执行。

最新优化更新

在最近的优化中,我们进一步改进了系统性能和稳定性:

  1. 送货单数据处理优化

    • 实现了 XkyService.GetSaveDetail() 方法的分批处理
    • 每批处理10条送货单数据,减少内存占用
    • 增强了异常处理,单条数据异常不会影响整批处理
  2. 异步任务处理增强

    • 为 DeliveryNoticeService 添加了 @Async 注解的异步处理方法
    • 实现了 processAsyncBatch 方法,支持并行处理多批数据
    • 优化了日志记录,便于问题排查
  3. 错误处理改进

    • 所有关键方法都添加了详细的日志记录
    • 实现了更细粒度的异常捕获和处理
    • 防止单个任务失败导致整个流程中断

异步执行流程示例

以下是一个典型的异步执行流程:

主线程: 开始处理100条数据
主线程: 将数据分为5批,每批20条
主线程: 提交批次1到异步线程池
主线程: 提交批次2到异步线程池
主线程: 提交批次3到异步线程池
主线程: 提交批次4到异步线程池
主线程: 提交批次5到异步线程池
主线程: 全部数据处理提交完成
异步线程1: 开始处理批次1
异步线程2: 开始处理批次2
异步线程3: 开始处理批次3
异步线程1: 批次1处理完成
异步线程1: 开始处理批次4
异步线程2: 批次2处理完成
异步线程2: 开始处理批次5
异步线程3: 批次3处理完成
异步线程1: 批次4处理完成
异步线程2: 批次5处理完成

这种方式确保了主线程不会被长时间阻塞,同时充分利用了系统资源进行并行处理。

钉钉消息通知功能

系统集成了钉钉消息通知功能,用于在重要事件发生时向指定用户发送通知。主要包括以下几个部分:

功能概述

  1. 通知场景

    • 采购订单数据比对完成后,通知相关人员查看结果
    • 数据异常时的预警通知
    • 系统重要操作的确认通知
    • 不合格检验单审批情况通知
  2. 通知方式

    • 个人工作通知:直接发送给指定用户
    • 群机器人通知:发送到指定的钉钉群

数据结构

钉钉用户信息存储在 DINGTALK_INFO 表中,表结构如下:

字段名 类型 说明
id Long 主键
sid Long 职工ID
phone String 电话号码
dingtalk_id String 钉钉用户ID
is_send_dingtalk Integer 是否发送钉钉通知(1:是, 0:否)

不合格检验单信息存储在 DINGTALK_MSG 表中,表结构如下:

字段名 类型 说明
release_no String 检验单号
supp_name String 供应商名称
create_date Date 来料日期
project_codes String 项目代码
item_no String 料号
fname String 审核人
fng_handle String 处理方式

消息发送流程

  1. 获取通知用户

    • DINGTALK_INFO 表中筛选 is_send_dingtalk 为1的用户
    • 如果用户的钉钉ID为空,则通过钉钉API根据手机号获取钉钉ID
    • 更新数据库中的钉钉ID
  2. 发送通知

    • 汇总所有有效的钉钉用户ID
    • 根据检验单号查询 DINGTALK_MSG 表获取消息内容
    • 调用钉钉开放API发送工作通知

配置说明

钉钉应用配置信息存储在 DataAcquisitionConfiguration 中:

// 钉钉应用Key
public static final String TALK_APP_KEY = "your_app_key";
// 钉钉应用Secret
public static final String TALK_APP_SECRET = "your_app_secret";
// 钉钉自定义机器人Token
public static final String CUSTOM_ROBOT_TOKEN = "your_robot_token";

使用方法

要发送钉钉通知,可以调用 DingtalkInfoServicesendMessage 方法:


@Autowired private DingtalkInfoService dingtalkInfoService; // 发送不合格检验单通知 boolean result = dingtalkInfoService.sendMessage("检验单号");

定时任务

系统配置了定时任务自动发送不合格检验单通知:

/**
 * 定时发送不合格检验单钉钉通知
 * 每小时检查一次是否有新的不合格检验单
 */
@Scheduled(cron = "0 0 */1 * * ?")
public void sendInspectionNotification() {
    // 任务实现...
}

消息格式

不合格检验单通知的格式如下:

供应商[xxx] 来料日期[yyyy-MM-dd] 项目[xxx] 料号[xxx]的不合格检验单被[xxx]审批为[xxx],请查收!

注意事项

  1. 钉钉消息发送频率有限制,请勿过于频繁发送
  2. 确保应用有发送工作通知的权限
  3. 用户手机号必须与钉钉注册手机号一致
  4. 建议将重要通知同时通过多种渠道发送(如邮件、短信等)
  5. 检验单通知在DINGTALK_MSG表中必须有对应的记录