| | |
| | | import CloseIcon from '@mui/icons-material/Close'; |
| | | import EditIcon from '@mui/icons-material/Edit'; |
| | | import TaskIcon from '@mui/icons-material/Task'; |
| | | import CloudUploadIcon from '@mui/icons-material/CloudUpload'; |
| | | import { styled } from '@mui/material/styles'; |
| | | import AsnOrderModal from "./AsnOrderModal"; |
| | | import request from '@/utils/request'; |
| | |
| | | <MyButton setCreateDialog={setCreateDialog} setmodalType={setmodalType} /> |
| | | {/* <InspectionButton /> 报检按钮暂不使用 */} |
| | | <CompleteButton /> |
| | | <CloudWmsAsnReportButton /> |
| | | <ODeleteButton /> |
| | | <PrintButton setPrintOrder={setPrintOrder} setSelect={setSelect} /> |
| | | {/* <CloseButton /> */} |
| | |
| | | } |
| | | export default AsnOrderList; |
| | | |
| | | /** manual:放行该单暂缓的云仓上报待办(send_hold);wait_order 一般由调度放行 */ |
| | | const CloudWmsAsnReportButton = () => { |
| | | const record = useRecordContext(); |
| | | const notify = useNotify(); |
| | | const refresh = useRefresh(); |
| | | const onClick = async (e) => { |
| | | e.stopPropagation(); |
| | | try { |
| | | const res = await request.post('/asnOrder/cloudWmsReport/submit', { code: record?.code }); |
| | | const { code, msg } = res.data || {}; |
| | | if (code === 200) { |
| | | notify(msg || '已提交', { type: 'success' }); |
| | | refresh(); |
| | | } else { |
| | | notify(msg || '操作失败', { type: 'warning' }); |
| | | } |
| | | } catch (err) { |
| | | notify(err?.message || '请求失败', { type: 'warning' }); |
| | | } |
| | | }; |
| | | return ( |
| | | <Button label="云仓上报" size="small" onClick={onClick} startIcon={<CloudUploadIcon />}/> |
| | | ); |
| | | }; |
| | | |
| | | //按PO单新建 |
| | | const CreateByPoButton = ({ setPoCreate }) => { |
| | | const record = useRecordContext(); |
| | |
| | | import TaskIcon from '@mui/icons-material/Task'; |
| | | import OutOrderPreview from "./OutOrderPreview"; |
| | | import AddIcon from '@mui/icons-material/Add'; |
| | | import CloudUploadIcon from '@mui/icons-material/CloudUpload'; |
| | | import OutStockPublic from "./OutStockPublic"; |
| | | import OutOrderModal from "./OutOrderModal"; |
| | | import request from '@/utils/request'; |
| | |
| | | {isInit && <MyButton setCreateDialog={setCreateDialog} setmodalType={setmodalType} />} |
| | | <EditButton label="toolbar.detail" icon={(<DetailsIcon />)} /> |
| | | <PublicButton setDrawerVal={setDrawerVal} drawerVal={drawerVal} setSelect={setSelect} /> |
| | | <CloudWmsOutReportButton /> |
| | | </> |
| | | ); |
| | | }; |
| | | |
| | | const CloudWmsOutReportButton = () => { |
| | | const record = useRecordContext(); |
| | | const notify = useNotify(); |
| | | const refresh = useRefresh(); |
| | | const onClick = async (e) => { |
| | | e.stopPropagation(); |
| | | try { |
| | | const res = await request.post('/outStock/cloudWmsReport/submit', { code: record?.code }); |
| | | const { code, msg } = res.data || {}; |
| | | if (code === 200) { |
| | | notify(msg || '已提交', { type: 'success' }); |
| | | refresh(); |
| | | } else { |
| | | notify(msg || '操作失败', { type: 'warning' }); |
| | | } |
| | | } catch (err) { |
| | | notify(err?.message || '请求失败', { type: 'warning' }); |
| | | } |
| | | }; |
| | | return ( |
| | | <Button label="云仓上报" size="small" onClick={onClick} startIcon={<CloudUploadIcon />} /> |
| | | ); |
| | | }; |
| | | |
| | | const MyButton = ({ setCreateDialog, setmodalType }) => { |
| | | const record = useRecordContext(); |
| | | const handleEditClick = (btn) => { |
| | |
| | | private String baseUrl; |
| | | |
| | | /** |
| | | * 鼎捷 ilcwmsplus 完成反馈等公共字段(orgNo、单据类别、单位) |
| | | * 鼎捷 ilcwmsplus 完成反馈等公共字段(单据类别、单位等) |
| | | */ |
| | | private Dap dap = new Dap(); |
| | | |
| | |
| | | |
| | | @Data |
| | | public static class Dap { |
| | | private String orgNo = ""; |
| | | private String docTypeIn = ""; |
| | | private String docTypeOut = ""; |
| | | /** 库存调整(9.2)单据类别;移库等 */ |
| | |
| | | public class DapIlcwmsCompletionLine { |
| | | |
| | | private String orgNo; |
| | | /** 任务仓库(与云仓通知单 docWarehouseNo 一致时可单独传递) */ |
| | | private String docWarehouseNo; |
| | | private String docType; |
| | | private String docNo; |
| | | private String docSeqNo; |
| New file |
| | |
| | | package com.vincent.rsf.server.api.controller.erp.params; |
| | | |
| | | import lombok.Data; |
| | | import lombok.experimental.Accessors; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * 云仓 9.1 入出库结果:一单多行合并上报时的请求体(与单条 InOutResultReportParam 区分) |
| | | */ |
| | | @Data |
| | | @Accessors(chain = true) |
| | | public class InOutResultBatchPayload { |
| | | |
| | | private List<InOutResultReportParam> lines; |
| | | } |
| | |
| | | @ApiModelProperty(value = "仓库编码", required = true) |
| | | private String wareHouseId; |
| | | |
| | | @ApiModelProperty("任务仓库(通知单存储,回馈云仓)") |
| | | private String docWarehouseNo; |
| | | |
| | | @ApiModelProperty("组织编码(通知单存储,回馈云仓)") |
| | | private String orgNo; |
| | | |
| | | @ApiModelProperty("入库仓编码(入库通知单存储,回馈 inWarehouseNo)") |
| | | private String inWarehouseNo; |
| | | |
| | | @ApiModelProperty("出库仓编码(出库通知单存储,回馈 outWarehouseNo)") |
| | | private String outWarehouseNo; |
| | | |
| | | @ApiModelProperty(value = "库位号", required = true) |
| | | private String locId; |
| | | |
| | |
| | | package com.vincent.rsf.server.api.controller.erp.params; |
| | | |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import com.fasterxml.jackson.annotation.JsonProperty; |
| | | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; |
| | | import io.swagger.annotations.ApiModel; |
| | | import io.swagger.annotations.ApiModelProperty; |
| | |
| | | @ApiModelProperty("入/出库接驳站点") |
| | | private String stationId; |
| | | |
| | | @ApiModelProperty("任务仓库(与云仓 JSON 字段 docWarehouseNo 对应)") |
| | | @JsonProperty("docWarehouseNo") |
| | | private String docTaskWarehouseNo; |
| | | |
| | | @ApiModelProperty("组织编码(orgNo)") |
| | | private String orgNo; |
| | | |
| | | @ApiModelProperty("入库仓编码(inWarehouseNo),入库通知单") |
| | | private String inWarehouseNo; |
| | | |
| | | @ApiModelProperty("出库仓编码(outWarehouseNo),出库通知单") |
| | | private String outWarehouseNo; |
| | | |
| | | @ApiModelProperty("原库位") |
| | | private String orgLoc; |
| | | |
| | |
| | | import com.vincent.rsf.server.api.controller.erp.params.InOutResultReportParam; |
| | | import com.vincent.rsf.server.api.controller.erp.params.InventoryAdjustReportParam; |
| | | |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | /** |
| | |
| | | Map<String, Object> reportInOutResult(InOutResultReportParam param); |
| | | |
| | | /** |
| | | * 9.1 入出库结果合并上报(一单多行) |
| | | */ |
| | | Map<String, Object> reportInOutResults(List<InOutResultReportParam> lines); |
| | | |
| | | /** |
| | | * 9.2 库存调整主动上报(立库侧调用云仓通知) |
| | | * @param param 上报参数 |
| | | * @return 云仓返回结构 Map:code, msg, data(data.result 为 SUCCESS/FAIL) |
| | |
| | | @Service |
| | | public class CloudWmsReportServiceImpl implements CloudWmsReportService { |
| | | |
| | | /** 云仓回馈:通知单 orgNo 为空时的默认组织 */ |
| | | private static final String DEFAULT_CLOUD_ORG_NO = "1"; |
| | | /** 云仓回馈:通知单 inWarehouseNo / outWarehouseNo 为空时的默认仓编码 */ |
| | | private static final String DEFAULT_CLOUD_WH_NO = "101"; |
| | | |
| | | @Autowired |
| | | private RemotesInfoProperties erpApi; |
| | | |
| | |
| | | @Override |
| | | public Map<String, Object> syncMatnrsToCloud(Object body) { |
| | | if (!isCloudWmsConfigured()) { |
| | | log.warn("ErpApi(云仓WMS) 未配置 host,跳过物料基础信息同步"); |
| | | log.warn("ErpApi(云仓WMS) 未配置 host/base-url,跳过物料基础信息同步"); |
| | | return stubSuccess("云仓地址未配置,未实际同步"); |
| | | } |
| | | return cloudWmsErpFeignClient.syncMatnrs(body != null ? body : new HashMap<>()); |
| | |
| | | return resultMap(400, "参数不能为空", null); |
| | | } |
| | | if (!isCloudWmsConfigured()) { |
| | | log.warn("ErpApi(云仓WMS) 未配置 host,跳过 9.1 入/出库结果上报,订单:{}", param.getOrderNo()); |
| | | log.warn("ErpApi(云仓WMS) 未配置 host/base-url,跳过 9.1 入/出库结果上报,订单:{}", param.getOrderNo()); |
| | | return stubSuccess("云仓地址未配置,未实际上报"); |
| | | } |
| | | String err = validateDapBase(); |
| | | String err = validateDapBaseForInOut(param); |
| | | if (err != null) { |
| | | return resultMap(400, err, null); |
| | | } |
| | |
| | | } |
| | | |
| | | @Override |
| | | public Map<String, Object> reportInOutResults(List<InOutResultReportParam> lines) { |
| | | if (lines == null || lines.isEmpty()) { |
| | | return resultMap(400, "明细不能为空", null); |
| | | } |
| | | if (!isCloudWmsConfigured()) { |
| | | log.warn("ErpApi(云仓WMS) 未配置 host/base-url,跳过 9.1 入出库合并上报"); |
| | | return stubSuccess("云仓地址未配置,未实际上报"); |
| | | } |
| | | InOutResultReportParam first = lines.get(0); |
| | | boolean inbound = first.getInbound() == null || Boolean.TRUE.equals(first.getInbound()); |
| | | for (InOutResultReportParam param : lines) { |
| | | String err = validateDapBaseForInOut(param); |
| | | if (err != null) { |
| | | return resultMap(400, err, null); |
| | | } |
| | | boolean rowIn = param.getInbound() == null || Boolean.TRUE.equals(param.getInbound()); |
| | | if (rowIn != inbound) { |
| | | return resultMap(400, "合并上报须同为入库或同为出库", null); |
| | | } |
| | | } |
| | | List<DapIlcwmsCompletionLine> data = new ArrayList<>(lines.size()); |
| | | for (InOutResultReportParam param : lines) { |
| | | data.add(buildInOutLine(param, inbound)); |
| | | } |
| | | DapIlcwmsCompletionRequest req = new DapIlcwmsCompletionRequest().setData(data); |
| | | logOutboundPayload("IN_OUT_RESULT_BATCH", inbound ? "stockInCompleted" : "stockOutCompleted", req); |
| | | Map<String, Object> raw = inbound |
| | | ? cloudWmsErpFeignClient.cusInventoryCompletionReport(req) |
| | | : cloudWmsErpFeignClient.cusOutboundCompletionReport(req); |
| | | return DapIlcwmsResponseNormalizer.toNotifyFormat(raw); |
| | | } |
| | | |
| | | @Override |
| | | public Map<String, Object> reportInventoryAdjust(InventoryAdjustReportParam param) { |
| | | if (param == null) { |
| | | return resultMap(400, "参数不能为空", null); |
| | | } |
| | | if (!isCloudWmsConfigured()) { |
| | | log.warn("ErpApi(云仓WMS) 未配置 host,跳过 9.2 库存调整上报,物料:{}", param.getMatNr()); |
| | | log.warn("ErpApi(云仓WMS) 未配置 host/base-url,跳过 9.2 库存调整上报,物料:{}", param.getMatNr()); |
| | | return stubSuccess("云仓地址未配置,未实际上报"); |
| | | } |
| | | String err = validateDapBase(); |
| | | if (err != null) { |
| | | return resultMap(400, err, null); |
| | | } |
| | | Integer changeType = param.getChangeType(); |
| | | if (changeType == null) { |
| | |
| | | : DapIlcwmsResponseNormalizer.toNotifyFormatFlexible(raw); |
| | | } |
| | | |
| | | private String validateDapBaseForInOut(InOutResultReportParam param) { |
| | | if (param != null && StringUtils.isBlank(param.getUnitNo())) { |
| | | return "unitNo 不能为空"; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private DapIlcwmsCompletionLine buildInOutLine(InOutResultReportParam param, boolean inbound) { |
| | | RemotesInfoProperties.Dap dap = erpApi.getDap(); |
| | | CloudMatnrParts matnrParts = parseCloudMatnr(param.getMatNr()); |
| | | String docType = StringUtils.isNotBlank(param.getWkType()) |
| | | ? param.getWkType() |
| | | : (inbound ? "IN" : "OUT"); |
| | | String unitNo = StringUtils.isNotBlank(param.getUnitNo()) ? param.getUnitNo() : "PCS"; |
| | | String unitNo = StringUtils.trimToEmpty(param.getUnitNo()); |
| | | String orgNoLine = StringUtils.isNotBlank(param.getOrgNo()) ? param.getOrgNo().trim() : DEFAULT_CLOUD_ORG_NO; |
| | | String docWh = StringUtils.trimToNull(param.getDocWarehouseNo()); |
| | | String inWhLine = StringUtils.isNotBlank(param.getInWarehouseNo()) ? param.getInWarehouseNo().trim() : DEFAULT_CLOUD_WH_NO; |
| | | String outWhLine = StringUtils.isNotBlank(param.getOutWarehouseNo()) ? param.getOutWarehouseNo().trim() : DEFAULT_CLOUD_WH_NO; |
| | | DapIlcwmsCompletionLine line = new DapIlcwmsCompletionLine() |
| | | .setOrgNo(dap.getOrgNo()) |
| | | .setOrgNo(orgNoLine) |
| | | .setDocWarehouseNo(docWh) |
| | | .setDocType(docType) |
| | | .setDocNo(param.getOrderNo()) |
| | | .setDocSeqNo(StringUtils.isNotBlank(param.getLineId()) ? param.getLineId() : "1") |
| | |
| | | .setCombinationLotNo(matnrParts.getCombinationLotNo()) |
| | | .setBarcode(matnrParts.getBarcode()); |
| | | if (inbound) { |
| | | line.setInWarehouseNo(param.getWareHouseId()).setInCellNo(param.getLocId()); |
| | | line.setInWarehouseNo(inWhLine).setInCellNo(param.getLocId()); |
| | | } else { |
| | | line.setOutWarehouseNo(param.getWareHouseId()).setOutCellNo(param.getLocId()); |
| | | line.setOutWarehouseNo(outWhLine).setOutCellNo(param.getLocId()); |
| | | } |
| | | return line; |
| | | } |
| | |
| | | * @param docSeqOverride 非空时用作项次(移库第二行等) |
| | | */ |
| | | private DapIlcwmsCompletionLine buildAdjustLine(InventoryAdjustReportParam param, boolean fillIn, boolean fillOut, String docSeqOverride) { |
| | | RemotesInfoProperties.Dap dap = erpApi.getDap(); |
| | | String docType = resolveAdjustDocType(param); |
| | | String docNo = StringUtils.isNotBlank(param.getDocNo()) ? param.getDocNo() : "ADJ"; |
| | | String docSeq = docSeqOverride != null ? docSeqOverride |
| | |
| | | String unit = StringUtils.isNotBlank(param.getUnitNo()) ? param.getUnitNo() : "PCS"; |
| | | CloudMatnrParts matnrParts = parseCloudMatnr(param.getMatNr()); |
| | | DapIlcwmsCompletionLine line = new DapIlcwmsCompletionLine() |
| | | .setOrgNo(dap.getOrgNo()) |
| | | .setOrgNo(DEFAULT_CLOUD_ORG_NO) |
| | | .setDocType(docType) |
| | | .setDocNo(docNo) |
| | | .setDocSeqNo(docSeq) |
| | |
| | | } |
| | | } |
| | | |
| | | private String validateDapBase() { |
| | | RemotesInfoProperties.Dap d = erpApi.getDap(); |
| | | if (d == null || StringUtils.isBlank(d.getOrgNo())) { |
| | | return "未配置 platform.erp.dap.org-no"; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private boolean isCloudWmsConfigured() { |
| | | String host = erpApi.getHost(); |
| | | return host != null && !host.trim().isEmpty(); |
| | | if (host != null && !host.trim().isEmpty()) { |
| | | return true; |
| | | } |
| | | String baseUrl = erpApi.getBaseUrl(); |
| | | return baseUrl != null && !baseUrl.trim().isEmpty(); |
| | | } |
| | | |
| | | private Map<String, Object> stubSuccess(String msg) { |
| | |
| | | .setUpdateTime(new Date()) |
| | | .setCreateBy(loginUserId) |
| | | .setUpdateBy(loginUserId); |
| | | if (StringUtils.isNotBlank(syncOrder.getDocTaskWarehouseNo())) { |
| | | wkOrder.setDocTaskWarehouseNo(syncOrder.getDocTaskWarehouseNo().trim()); |
| | | } |
| | | if (StringUtils.isNotBlank(syncOrder.getOrgNo())) { |
| | | wkOrder.setDocOrgNo(syncOrder.getOrgNo().trim()); |
| | | } |
| | | String effTypeForDoc = resolvedOrderType != null ? resolvedOrderType : StringUtils.trimToNull(syncOrder.getType()); |
| | | if (OrderType.ORDER_IN.type.equals(effTypeForDoc) && StringUtils.isNotBlank(syncOrder.getInWarehouseNo())) { |
| | | wkOrder.setDocInWarehouseNo(syncOrder.getInWarehouseNo().trim()); |
| | | } |
| | | if (OrderType.ORDER_OUT.type.equals(effTypeForDoc) && StringUtils.isNotBlank(syncOrder.getOutWarehouseNo())) { |
| | | wkOrder.setDocOutWarehouseNo(syncOrder.getOutWarehouseNo().trim()); |
| | | } |
| | | |
| | | if (resolvedOrderType != null && resolvedOrderType.equals(OrderType.ORDER_OUT.type)) { |
| | | wkOrder.setExceStatus(AsnExceStatus.OUT_STOCK_STATUS_TASK_INIT.val); |
| | |
| | | return true; |
| | | } |
| | | } |
| | | if (StringUtils.isNotBlank(syncOrder.getOrgNo())) { |
| | | if (!StringUtils.equals(syncOrder.getOrgNo().trim(), StringUtils.trimToNull(order.getDocOrgNo()))) { |
| | | return true; |
| | | } |
| | | } |
| | | if (StringUtils.isNotBlank(syncOrder.getDocTaskWarehouseNo())) { |
| | | if (!StringUtils.equals(syncOrder.getDocTaskWarehouseNo().trim(), StringUtils.trimToNull(order.getDocTaskWarehouseNo()))) { |
| | | return true; |
| | | } |
| | | } |
| | | if (OrderType.ORDER_IN.type.equals(order.getType()) && StringUtils.isNotBlank(syncOrder.getInWarehouseNo())) { |
| | | if (!StringUtils.equals(syncOrder.getInWarehouseNo().trim(), StringUtils.trimToNull(order.getDocInWarehouseNo()))) { |
| | | return true; |
| | | } |
| | | } |
| | | if (OrderType.ORDER_OUT.type.equals(order.getType()) && StringUtils.isNotBlank(syncOrder.getOutWarehouseNo())) { |
| | | if (!StringUtils.equals(syncOrder.getOutWarehouseNo().trim(), StringUtils.trimToNull(order.getDocOutWarehouseNo()))) { |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | |
| | | if (StringUtils.isNotBlank(syncOrder.getStationId())) { |
| | | order.setStationId(syncOrder.getStationId()); |
| | | } |
| | | if (StringUtils.isNotBlank(syncOrder.getDocTaskWarehouseNo())) { |
| | | order.setDocTaskWarehouseNo(syncOrder.getDocTaskWarehouseNo().trim()); |
| | | } |
| | | if (StringUtils.isNotBlank(syncOrder.getOrgNo())) { |
| | | order.setDocOrgNo(syncOrder.getOrgNo().trim()); |
| | | } |
| | | if (OrderType.ORDER_IN.type.equals(order.getType()) && StringUtils.isNotBlank(syncOrder.getInWarehouseNo())) { |
| | | order.setDocInWarehouseNo(syncOrder.getInWarehouseNo().trim()); |
| | | } |
| | | if (OrderType.ORDER_OUT.type.equals(order.getType()) && StringUtils.isNotBlank(syncOrder.getOutWarehouseNo())) { |
| | | order.setDocOutWarehouseNo(syncOrder.getOutWarehouseNo().trim()); |
| | | } |
| | | order.setUpdateBy(loginUserId); |
| | | order.setUpdateTime(new Date()); |
| | | asnOrderService.updateById(order); |
| | |
| | | if (StringUtils.isNotBlank(syncOrder.getStationId())) { |
| | | order.setStationId(syncOrder.getStationId()); |
| | | } |
| | | if (StringUtils.isNotBlank(syncOrder.getDocTaskWarehouseNo())) { |
| | | order.setDocTaskWarehouseNo(syncOrder.getDocTaskWarehouseNo().trim()); |
| | | } |
| | | if (StringUtils.isNotBlank(syncOrder.getOrgNo())) { |
| | | order.setDocOrgNo(syncOrder.getOrgNo().trim()); |
| | | } |
| | | if (OrderType.ORDER_IN.type.equals(order.getType()) && StringUtils.isNotBlank(syncOrder.getInWarehouseNo())) { |
| | | order.setDocInWarehouseNo(syncOrder.getInWarehouseNo().trim()); |
| | | } |
| | | if (OrderType.ORDER_OUT.type.equals(order.getType()) && StringUtils.isNotBlank(syncOrder.getOutWarehouseNo())) { |
| | | order.setDocOutWarehouseNo(syncOrder.getOutWarehouseNo().trim()); |
| | | } |
| | | order.setUpdateBy(loginUserId); |
| | | order.setUpdateTime(new Date()); |
| | | asnOrderService.updateById(order); |
| | |
| | | import com.vincent.rsf.common.utils.Serialize; |
| | | import com.vincent.rsf.server.common.config.RedisProperties; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.commons.lang3.StringUtils; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.stereotype.Service; |
| | | import redis.clients.jedis.Jedis; |
| | |
| | | JedisPoolConfig config = new JedisPoolConfig(); |
| | | config.setTestOnBorrow(false); |
| | | this.index = redisProperties.getIndex(); |
| | | // 空白密码传 null,不向未开认证的 Redis 发 AUTH |
| | | String pwd = StringUtils.trimToNull(redisProperties.getPassword()); |
| | | this.pool = new JedisPool(config |
| | | , redisProperties.getHost() |
| | | , redisProperties.getPort() |
| | | , redisProperties.getTimeout() |
| | | , redisProperties.getPassword() |
| | | , pwd |
| | | ); |
| | | } |
| | | return this.pool; |
| | | } |
| | | |
| | | /** 借连接失败时返回 null */ |
| | | public Jedis getJedis(){ |
| | | try{ |
| | | Jedis jedis = this.getPool().getResource(); |
| | |
| | | return null; |
| | | } |
| | | Jedis jedis = this.getJedis(); |
| | | if (jedis == null) { |
| | | return null; |
| | | } |
| | | try{ |
| | | return jedis.set((flag + LINK + key).getBytes(), Serialize.serialize(value)); |
| | | } catch (Exception e) { |
| | |
| | | return null; |
| | | } |
| | | Jedis jedis = this.getJedis(); |
| | | if (jedis == null) { |
| | | return null; |
| | | } |
| | | try{ |
| | | return jedis.setex((flag + LINK + key).getBytes(), seconds, Serialize.serialize(value)); |
| | | } catch (Exception e) { |
| | |
| | | return null; |
| | | } |
| | | Jedis jedis = this.getJedis(); |
| | | if (jedis == null) { |
| | | return null; |
| | | } |
| | | try{ |
| | | byte[] bytes = jedis.get((flag + LINK + key).getBytes()); |
| | | if(bytes == null || bytes.length == 0 ) { |
| | |
| | | return null; |
| | | } |
| | | Jedis jedis = this.getJedis(); |
| | | if (jedis == null) { |
| | | return null; |
| | | } |
| | | try{ |
| | | return jedis.del((flag + LINK + key).getBytes()); |
| | | } catch (Exception e) { |
| | |
| | | return null; |
| | | } |
| | | Jedis jedis = this.getJedis(); |
| | | if (jedis == null) { |
| | | return null; |
| | | } |
| | | this.setValue(flag, "CLEARING", "true"); |
| | | try{ |
| | | Object returnValue = jedis.eval("local keys = redis.call('keys', ARGV[1]) for i=1,#keys,1000 do redis.call('del', unpack(keys, i, math.min(i+4999, #keys))) end return #keys",0,flag + LINK + "*"); |
| | |
| | | return; |
| | | } |
| | | Jedis jedis = this.getJedis(); |
| | | if (jedis == null) { |
| | | return; |
| | | } |
| | | try{ |
| | | jedis.expire((flag + LINK + key).getBytes(), seconds); |
| | | } catch (Exception e) { |
| | |
| | | return; |
| | | } |
| | | Jedis jedis = this.getJedis(); |
| | | if (jedis == null) { |
| | | return; |
| | | } |
| | | try{ |
| | | jedis.expireAt((flag + LINK + key).getBytes(), toTime.getTime()/1000); |
| | | } catch (Exception e) { |
| | |
| | | return null; |
| | | } |
| | | Jedis jedis = this.getJedis(); |
| | | if (jedis == null) { |
| | | return null; |
| | | } |
| | | try{ |
| | | return jedis.ttl((flag + LINK + key).getBytes()); |
| | | } catch (Exception e) { |
| | |
| | | return null; |
| | | } |
| | | Jedis jedis = this.getJedis(); |
| | | if (jedis == null) { |
| | | return null; |
| | | } |
| | | try{ |
| | | return jedis.set(flag + LINK + key, value); |
| | | } catch (Exception e) { |
| | |
| | | return null; |
| | | } |
| | | Jedis jedis = this.getJedis(); |
| | | if (jedis == null) { |
| | | return null; |
| | | } |
| | | try{ |
| | | return jedis.setex(flag + LINK + key, seconds , value); |
| | | } catch (Exception e) { |
| | |
| | | return null; |
| | | } |
| | | Jedis jedis = this.getJedis(); |
| | | if (jedis == null) { |
| | | return null; |
| | | } |
| | | try{ |
| | | return jedis.get(flag + LINK + key); |
| | | } catch (Exception e) { |
| | |
| | | } |
| | | |
| | | Jedis jedis = this.getJedis(); |
| | | if (jedis == null) { |
| | | return null; |
| | | } |
| | | try{ |
| | | String[] keys = new String[key.length]; |
| | | for(int i=0;i<key.length;i++){ |
| | |
| | | |
| | | this.setValue(flag, "CLEARING", "true"); |
| | | Jedis jedis = this.getJedis(); |
| | | if (jedis == null) { |
| | | return null; |
| | | } |
| | | |
| | | try{ |
| | | Object returnValue = jedis.eval("return redis.call('del', unpack(redis.call('keys', ARGV[1])))",0,flag + LINK + "*"); |
| | |
| | | return; |
| | | } |
| | | Jedis jedis = this.getJedis(); |
| | | if (jedis == null) { |
| | | return; |
| | | } |
| | | try{ |
| | | jedis.expire((flag + LINK + key).getBytes(), seconds); |
| | | } catch (Exception e) { |
| | |
| | | return; |
| | | } |
| | | Jedis jedis = this.getJedis(); |
| | | if (jedis == null) { |
| | | return; |
| | | } |
| | | try{ |
| | | jedis.expireAt((flag + LINK + key).getBytes(), atTime.getTime()/1000); |
| | | } catch (Exception e) { |
| | |
| | | return null; |
| | | } |
| | | Jedis jedis = this.getJedis(); |
| | | if (jedis == null) { |
| | | return null; |
| | | } |
| | | try { |
| | | return jedis.hset(name.getBytes(), key.getBytes(), Serialize.serialize(value)); |
| | | } catch (Exception e) { |
| | |
| | | return null; |
| | | } |
| | | Jedis jedis = this.getJedis(); |
| | | if (jedis == null) { |
| | | return null; |
| | | } |
| | | try{ |
| | | byte[] bytes = jedis.hget(name.getBytes(), key.getBytes()); |
| | | if (bytes == null || bytes.length == 0) { |
| | |
| | | return null; |
| | | } |
| | | Jedis jedis = this.getJedis(); |
| | | if (jedis == null) { |
| | | return null; |
| | | } |
| | | try{ |
| | | return jedis.hkeys(name); |
| | | } catch (Exception e) { |
| | |
| | | return null; |
| | | } |
| | | Jedis jedis = this.getJedis(); |
| | | if (jedis == null) { |
| | | return null; |
| | | } |
| | | try{ |
| | | String[] keys = new String[key.length]; |
| | | System.arraycopy(key, 0, keys, 0, key.length); |
| | |
| | | return null; |
| | | } |
| | | Jedis jedis = this.getJedis(); |
| | | if (jedis == null) { |
| | | return null; |
| | | } |
| | | try{ |
| | | return jedis.del(name); |
| | | } catch (Exception e) { |
| | |
| | | return; |
| | | } |
| | | Jedis jedis = this.getJedis(); |
| | | if (jedis == null) { |
| | | return; |
| | | } |
| | | try{ |
| | | jedis.expire(name.getBytes(), seconds); |
| | | } catch (Exception e) { |
| | |
| | | return; |
| | | } |
| | | Jedis jedis = this.getJedis(); |
| | | if (jedis == null) { |
| | | return; |
| | | } |
| | | try{ |
| | | jedis.expireAt(name.getBytes(), atTime.getTime()/1000); |
| | | } catch (Exception e) { |
| | |
| | | return null; |
| | | } |
| | | Jedis jedis = this.getJedis(); |
| | | if (jedis == null) { |
| | | return null; |
| | | } |
| | | try{ |
| | | return jedis.rpush(name.getBytes(), Serialize.serialize(value)); |
| | | } catch (Exception e) { |
| | |
| | | return null; |
| | | } |
| | | Jedis jedis = this.getJedis(); |
| | | if (jedis == null) { |
| | | return null; |
| | | } |
| | | try{ |
| | | byte[] bytes = jedis.lpop(name.getBytes()); |
| | | if(bytes == null || bytes.length == 0) { |
| | |
| | | return null; |
| | | } |
| | | Jedis jedis = this.getJedis(); |
| | | if (jedis == null) { |
| | | return null; |
| | | } |
| | | try{ |
| | | return jedis.del(name); |
| | | } catch (Exception e) { |
| | |
| | | return; |
| | | } |
| | | Jedis jedis = this.getJedis(); |
| | | if (jedis == null) { |
| | | return; |
| | | } |
| | | try{ |
| | | jedis.expire(name.getBytes(), seconds); |
| | | } catch (Exception e) { |
| | |
| | | return; |
| | | } |
| | | Jedis jedis = this.getJedis(); |
| | | if (jedis == null) { |
| | | return; |
| | | } |
| | | try{ |
| | | jedis.expireAt(name.getBytes(), atTime.getTime()/1000); |
| | | } catch (Exception e) { |
| | |
| | | return null; |
| | | } |
| | | Jedis jedis = this.getJedis(); |
| | | if (jedis == null) { |
| | | return null; |
| | | } |
| | | try{ |
| | | return jedis.incr("COUNT." + key); |
| | | } catch (Exception e) { |
| | |
| | | return null; |
| | | } |
| | | Jedis jedis = this.getJedis(); |
| | | if (jedis == null) { |
| | | return null; |
| | | } |
| | | try{ |
| | | return jedis.decr("COUNT." + key); |
| | | } catch (Exception e) { |
| | |
| | | return null; |
| | | } |
| | | |
| | | /** SET NX EX;键已存在返回 false;未初始化或借连接失败返回 true,由调用方降级 */ |
| | | public boolean trySetStringNxEx(String flag, String key, String value, int expireSeconds) { |
| | | if (!this.initialize) { |
| | | return true; |
| | | } |
| | | Jedis jedis = this.getJedis(); |
| | | if (jedis == null) { |
| | | return true; |
| | | } |
| | | try { |
| | | String fullKey = flag + LINK + key; |
| | | String r = jedis.set(fullKey, value, "NX", "EX", expireSeconds); |
| | | return "OK".equals(r); |
| | | } catch (Exception e) { |
| | | log.error(this.getClass().getSimpleName(), e); |
| | | return true; |
| | | } |
| | | } |
| | | |
| | | /** SETNX + EXPIRE;未抢到锁返回 false;jedis 异常时降级为 true */ |
| | | public boolean tryLockValue(String flag, String key, int expireSeconds) { |
| | | if(!this.initialize) { |
| | | return true; |
| | | } |
| | | Jedis jedis = this.getJedis(); |
| | | if (jedis == null) { |
| | | return true; |
| | | } |
| | | try { |
| | | String lockKey = flag + LINK + key; |
| | | Long r = jedis.setnx(lockKey, "1"); |
| | | if (r != null && r == 1L) { |
| | | jedis.expire(lockKey, expireSeconds); |
| | | return true; |
| | | } |
| | | return false; |
| | | } catch (Exception e) { |
| | | log.error(this.getClass().getSimpleName(), e); |
| | | return true; |
| | | } |
| | | } |
| | | |
| | | public void unlockValue(String flag, String key) { |
| | | deleteValue(flag, key); |
| | | } |
| | | |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.server.manager.constant; |
| | | |
| | | /** |
| | | * 云仓入出库回馈模式(sys_config CLOUD_WMS_INOUT_REPORT_MODE,val 小写) |
| | | */ |
| | | public final class CloudWmsInoutReportMode { |
| | | |
| | | /** 按任务行立即落待办(与改造前一致) */ |
| | | public static final String IMMEDIATE = "immediate"; |
| | | |
| | | /** 实绩写入 notify 待办且暂缓发送;整单无执行中任务且防抖后放行,或超过「无操作秒数」放行 */ |
| | | public static final String WAIT_ORDER = "wait_order"; |
| | | |
| | | /** 实绩写入 notify 待办且暂缓发送,由入库/出库通知单「上报云仓」放行发送 */ |
| | | public static final String MANUAL = "manual"; |
| | | |
| | | private CloudWmsInoutReportMode() { |
| | | } |
| | | } |
| | |
| | | import com.vincent.rsf.server.manager.controller.params.AsnOrderAndItemsParams; |
| | | import com.vincent.rsf.server.manager.entity.excel.AsnOrderTemplate; |
| | | import com.vincent.rsf.server.manager.enums.AsnExceStatus; |
| | | import com.vincent.rsf.server.manager.service.CloudWmsNotifyLogService; |
| | | import com.vincent.rsf.server.manager.service.OutStockItemService; |
| | | import com.vincent.rsf.server.manager.service.OutStockService; |
| | | import com.vincent.rsf.server.system.constant.SerialRuleCode; |
| | |
| | | private OutStockService outStockService; |
| | | @Autowired |
| | | private OutStockItemService outStockItemService; |
| | | @Autowired |
| | | private CloudWmsNotifyLogService cloudWmsNotifyLogService; |
| | | |
| | | @ApiOperation("手动触发云仓回馈(出库通知单,放行暂缓上报)") |
| | | @PostMapping("/outStock/cloudWmsReport/submit") |
| | | @PreAuthorize("hasAuthority('manager:outStock:list')") |
| | | public R submitCloudWmsReportOutbound(@RequestBody(required = false) Map<String, Object> body) { |
| | | String code = body != null && body.get("code") != null ? String.valueOf(body.get("code")).trim() : null; |
| | | return cloudWmsNotifyLogService.manualFlushToNotifyByOrderCode(code, false); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:outStock:list')") |
| | | @PostMapping("/outStock/page") |
| | |
| | | import com.vincent.rsf.server.manager.entity.excel.AsnOrderTemplate; |
| | | import com.vincent.rsf.server.manager.service.AsnOrderItemService; |
| | | import com.vincent.rsf.server.manager.service.AsnOrderService; |
| | | import com.vincent.rsf.server.manager.service.CloudWmsNotifyLogService; |
| | | import com.vincent.rsf.server.manager.service.impl.PurchaseItemServiceImpl; |
| | | import com.vincent.rsf.server.manager.service.impl.PurchaseServiceImpl; |
| | | import com.vincent.rsf.server.system.constant.SerialRuleCode; |
| | |
| | | private PurchaseServiceImpl purchaseService; |
| | | @Autowired |
| | | private PurchaseItemServiceImpl purchaseItemService; |
| | | @Autowired |
| | | private CloudWmsNotifyLogService cloudWmsNotifyLogService; |
| | | |
| | | @ApiOperation("手动触发云仓回馈(入库通知单,放行暂缓上报)") |
| | | @PostMapping("/asnOrder/cloudWmsReport/submit") |
| | | @PreAuthorize("hasAuthority('manager:asnOrder:list')") |
| | | public R submitCloudWmsReportInbound(@RequestBody(required = false) Map<String, Object> body) { |
| | | String code = body != null && body.get("code") != null ? String.valueOf(body.get("code")).trim() : null; |
| | | return cloudWmsNotifyLogService.manualFlushToNotifyByOrderCode(code, true); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:asnOrder:list')") |
| | | @PostMapping("/asnOrder/page") |
| | |
| | | @ApiModelProperty("业务关联(如 taskId、reviseLogId,便于排查)") |
| | | private String bizRef; |
| | | |
| | | @ApiModelProperty("云仓单据号(入出库索引)") |
| | | private String sourceOrderNo; |
| | | |
| | | @ApiModelProperty("1入库 0出库") |
| | | private Integer inboundFlag; |
| | | |
| | | @ApiModelProperty("云仓仓库编码") |
| | | private String wareHouseCode; |
| | | |
| | | @ApiModelProperty("1暂缓调度发送(manual/wait_order 未放行)") |
| | | private Integer sendHold; |
| | | |
| | | @ApiModelProperty("1正在上报中,合并任务排除") |
| | | private Integer sending; |
| | | |
| | | @ApiModelProperty("租户") |
| | | private Integer tenantId; |
| | | |
| | |
| | | @com.baomidou.mybatisplus.annotation.TableField("station_id") |
| | | private String stationId; |
| | | |
| | | /** 云仓下发:任务仓库(与回馈报文 docWarehouseNo 一致) */ |
| | | @ApiModelProperty("任务仓库") |
| | | @com.baomidou.mybatisplus.annotation.TableField("doc_task_warehouse_no") |
| | | private String docTaskWarehouseNo; |
| | | |
| | | /** 云仓下发:组织编码(回馈 orgNo) */ |
| | | @ApiModelProperty("组织编码") |
| | | @com.baomidou.mybatisplus.annotation.TableField("doc_org_no") |
| | | private String docOrgNo; |
| | | |
| | | /** 云仓下发:入库仓编码(回馈 inWarehouseNo) */ |
| | | @ApiModelProperty("入库仓编码") |
| | | @com.baomidou.mybatisplus.annotation.TableField("doc_in_warehouse_no") |
| | | private String docInWarehouseNo; |
| | | |
| | | /** 云仓下发:出库仓编码(回馈 outWarehouseNo) */ |
| | | @ApiModelProperty("出库仓编码") |
| | | @com.baomidou.mybatisplus.annotation.TableField("doc_out_warehouse_no") |
| | | private String docOutWarehouseNo; |
| | | |
| | | /** |
| | | * 单据类型 |
| | | */ |
| New file |
| | |
| | | package com.vincent.rsf.server.manager.schedules; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
| | | import com.vincent.rsf.server.manager.constant.CloudWmsInoutReportMode; |
| | | import com.vincent.rsf.server.manager.entity.CloudWmsNotifyLog; |
| | | import com.vincent.rsf.server.manager.service.CloudWmsNotifyLogService; |
| | | import com.vincent.rsf.server.manager.service.impl.CloudWmsOrderTaskRunningHelper; |
| | | import com.vincent.rsf.server.system.constant.GlobalConfigCode; |
| | | import com.vincent.rsf.server.system.entity.Config; |
| | | import com.vincent.rsf.server.system.service.ConfigService; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.commons.lang3.StringUtils; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.scheduling.annotation.Scheduled; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.Date; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | /** |
| | | * wait_order:按空闲/整单无执行中任务 + 防抖,将暂缓发送的入出库待办放行(数据在 man_cloud_wms_notify_log) |
| | | */ |
| | | @Slf4j |
| | | @Component |
| | | public class CloudWmsInoutAggSchedule { |
| | | |
| | | private static final int DEFAULT_IDLE_SEC = 180; |
| | | private static final int DEFAULT_DEBOUNCE_SEC = 8; |
| | | |
| | | @Autowired |
| | | private CloudWmsNotifyLogService cloudWmsNotifyLogService; |
| | | @Autowired |
| | | private CloudWmsOrderTaskRunningHelper cloudWmsOrderTaskRunningHelper; |
| | | @Autowired |
| | | private ConfigService configService; |
| | | |
| | | @Scheduled(cron = "0/15 * * * * ?") |
| | | public void releaseHeldInOutNotifies() { |
| | | String mode = resolveMode(); |
| | | if (!CloudWmsInoutReportMode.WAIT_ORDER.equals(mode)) { |
| | | return; |
| | | } |
| | | int idleSec = resolveInt(GlobalConfigCode.CLOUD_WMS_INOUT_AGG_IDLE_SECONDS, DEFAULT_IDLE_SEC); |
| | | int debounceSec = resolveInt(GlobalConfigCode.CLOUD_WMS_INOUT_AGG_COMPLETE_DEBOUNCE_SECONDS, DEFAULT_DEBOUNCE_SEC); |
| | | long nowMs = System.currentTimeMillis(); |
| | | String rt = cloudWmsNotifyLogService.getReportTypeInOutResult(); |
| | | int pending = cloudWmsNotifyLogService.getNotifyStatusPending(); |
| | | int fail = cloudWmsNotifyLogService.getNotifyStatusFail(); |
| | | List<CloudWmsNotifyLog> heldRows = cloudWmsNotifyLogService.list(new LambdaQueryWrapper<CloudWmsNotifyLog>() |
| | | .eq(CloudWmsNotifyLog::getReportType, rt) |
| | | .eq(CloudWmsNotifyLog::getSendHold, 1) |
| | | .in(CloudWmsNotifyLog::getNotifyStatus, pending, fail) |
| | | .isNotNull(CloudWmsNotifyLog::getSourceOrderNo) |
| | | .apply("(max_retry_count IS NULL OR max_retry_count = -1 OR retry_count < max_retry_count)")); |
| | | if (heldRows.isEmpty()) { |
| | | return; |
| | | } |
| | | Map<String, List<CloudWmsNotifyLog>> groups = new LinkedHashMap<>(); |
| | | for (CloudWmsNotifyLog row : heldRows) { |
| | | if (row == null) { |
| | | continue; |
| | | } |
| | | String key = row.getSourceOrderNo() + "\t" + StringUtils.defaultString(String.valueOf(row.getInboundFlag())) |
| | | + "\t" + StringUtils.defaultString(row.getWareHouseCode()); |
| | | groups.computeIfAbsent(key, k -> new ArrayList<>()).add(row); |
| | | } |
| | | Date now = new Date(); |
| | | for (List<CloudWmsNotifyLog> group : groups.values()) { |
| | | if (group.isEmpty()) { |
| | | continue; |
| | | } |
| | | CloudWmsNotifyLog first = group.get(0); |
| | | long maxUt = 0L; |
| | | for (CloudWmsNotifyLog r : group) { |
| | | Date u = r.getUpdateTime() != null ? r.getUpdateTime() : r.getCreateTime(); |
| | | if (u != null) { |
| | | maxUt = Math.max(maxUt, u.getTime()); |
| | | } |
| | | } |
| | | long ageSec = maxUt <= 0 ? 0 : Math.max(0L, (nowMs - maxUt) / 1000L); |
| | | boolean running = cloudWmsOrderTaskRunningHelper.hasRunningTasksForPlatOrder(first.getSourceOrderNo()); |
| | | boolean idleFlush = ageSec >= idleSec; |
| | | boolean completeFlush = !running && ageSec >= debounceSec; |
| | | if (!idleFlush && !completeFlush) { |
| | | continue; |
| | | } |
| | | List<Long> ids = new ArrayList<>(group.size()); |
| | | for (CloudWmsNotifyLog r : group) { |
| | | if (r.getId() != null) { |
| | | ids.add(r.getId()); |
| | | } |
| | | } |
| | | if (ids.isEmpty()) { |
| | | continue; |
| | | } |
| | | try { |
| | | LambdaUpdateWrapper<CloudWmsNotifyLog> u = new LambdaUpdateWrapper<>(); |
| | | u.in(CloudWmsNotifyLog::getId, ids) |
| | | .set(CloudWmsNotifyLog::getSendHold, 0) |
| | | .set(CloudWmsNotifyLog::getUpdateTime, now); |
| | | cloudWmsNotifyLogService.update(u); |
| | | } catch (Exception e) { |
| | | log.warn("云仓 wait_order 放行待办异常:{}", e.getMessage()); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private String resolveMode() { |
| | | try { |
| | | Config c = configService.getCachedOrLoad(GlobalConfigCode.CLOUD_WMS_INOUT_REPORT_MODE); |
| | | if (c != null && StringUtils.isNotBlank(c.getVal())) { |
| | | return c.getVal().trim().toLowerCase(); |
| | | } |
| | | } catch (Exception ignored) { |
| | | } |
| | | return CloudWmsInoutReportMode.IMMEDIATE; |
| | | } |
| | | |
| | | private int resolveInt(String flag, int defaultVal) { |
| | | try { |
| | | Config c = configService.getCachedOrLoad(flag); |
| | | if (c != null && StringUtils.isNotBlank(c.getVal())) { |
| | | int v = Integer.parseInt(c.getVal().trim()); |
| | | return v >= 0 ? v : defaultVal; |
| | | } |
| | | } catch (Exception ignored) { |
| | | } |
| | | return defaultVal; |
| | | } |
| | | } |
| | |
| | | package com.vincent.rsf.server.manager.schedules; |
| | | |
| | | import com.fasterxml.jackson.core.JsonProcessingException; |
| | | import com.fasterxml.jackson.databind.JsonNode; |
| | | import com.fasterxml.jackson.databind.ObjectMapper; |
| | | import com.vincent.rsf.server.api.controller.erp.params.InOutResultBatchPayload; |
| | | import com.vincent.rsf.server.api.controller.erp.params.InOutResultReportParam; |
| | | import com.vincent.rsf.server.api.controller.erp.params.InventoryAdjustReportParam; |
| | | import com.vincent.rsf.server.api.service.CloudWmsReportService; |
| | |
| | | import org.springframework.scheduling.annotation.Scheduled; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import java.io.IOException; |
| | | import java.util.ArrayList; |
| | | import java.util.Date; |
| | | import java.util.HashSet; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.stream.Collectors; |
| | | import java.util.Set; |
| | | |
| | | /** 云仓上报定时任务 */ |
| | | @Slf4j |
| | |
| | | @Autowired |
| | | private ConfigService configService; |
| | | |
| | | @Scheduled(cron = "0/30 * * * * ?") |
| | | /** sending=1 且 Redis 占位已失、update_time 超时:补偿清零 */ |
| | | @Scheduled(cron = "0 0/2 * * * ?") |
| | | // @Scheduled(cron = "0/5 * * * * ?") |
| | | public void recoverStaleSending() { |
| | | try { |
| | | cloudWmsNotifyLogService.recoverStaleSendingWhenRedisMiss(); |
| | | } catch (Exception e) { |
| | | log.warn("云仓 sending 补偿任务异常:{}", e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | @Scheduled(cron = "0/60 * * * * ?") |
| | | public void syncCloudWmsNotify() { |
| | | // List<CloudWmsNotifyLog> pending = cloudWmsNotifyLogService.listPending(BATCH_LIMIT, 999); |
| | | List<CloudWmsNotifyLog> pending = cloudWmsNotifyLogService.listPending(BATCH_LIMIT, -1); |
| | | if (pending.isEmpty()) { |
| | | log.debug("云仓上报调度:本轮待发送 0 条"); |
| | | return; |
| | | } |
| | | long nowMs = System.currentTimeMillis(); |
| | | List<CloudWmsNotifyLog> ready = pending.stream() |
| | | .filter(logRecord -> shouldProcess(logRecord, nowMs)) |
| | | .collect(Collectors.toList()); |
| | | ready.parallelStream().forEach(this::safeProcessOne); |
| | | log.info("云仓上报调度:本轮待发送 {} 条", pending.size()); |
| | | dispatchPending(pending); |
| | | } |
| | | |
| | | /** 同单多条合并上报 */ |
| | | private void dispatchPending(List<CloudWmsNotifyLog> pending) { |
| | | String rtInOut = cloudWmsNotifyLogService.getReportTypeInOutResult(); |
| | | LinkedHashMap<String, List<CloudWmsNotifyLog>> inOutGroups = new LinkedHashMap<>(); |
| | | for (CloudWmsNotifyLog row : pending) { |
| | | if (!rtInOut.equals(row.getReportType())) { |
| | | continue; |
| | | } |
| | | String key = cloudWmsNotifyLogService.inOutMergeKeyFromRequestBody(row.getRequestBody()); |
| | | if (key == null) { |
| | | continue; |
| | | } |
| | | inOutGroups.computeIfAbsent(key, k -> new ArrayList<>()).add(row); |
| | | } |
| | | Set<Long> done = new HashSet<>(); |
| | | for (CloudWmsNotifyLog row : pending) { |
| | | Long rid = row.getId(); |
| | | if (rid != null && done.contains(rid)) { |
| | | continue; |
| | | } |
| | | if (!rtInOut.equals(row.getReportType())) { |
| | | safeProcessOne(row); |
| | | if (rid != null) { |
| | | done.add(rid); |
| | | } |
| | | continue; |
| | | } |
| | | String key = cloudWmsNotifyLogService.inOutMergeKeyFromRequestBody(row.getRequestBody()); |
| | | if (key == null) { |
| | | safeProcessOne(row); |
| | | if (rid != null) { |
| | | done.add(rid); |
| | | } |
| | | continue; |
| | | } |
| | | List<CloudWmsNotifyLog> g = inOutGroups.get(key); |
| | | if (g != null && g.size() >= 2) { |
| | | safeProcessMergedInOutGroup(g); |
| | | for (CloudWmsNotifyLog x : g) { |
| | | if (x.getId() != null) { |
| | | done.add(x.getId()); |
| | | } |
| | | } |
| | | } else { |
| | | safeProcessOne(row); |
| | | if (rid != null) { |
| | | done.add(rid); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | private void safeProcessMergedInOutGroup(List<CloudWmsNotifyLog> group) { |
| | | List<Long> claimedIds = new ArrayList<>(); |
| | | try { |
| | | for (CloudWmsNotifyLog row : group) { |
| | | Long id = row.getId(); |
| | | if (!cloudWmsNotifyLogService.tryClaimSending(id)) { |
| | | log.debug("云仓上报同批合并未抢到发送权 id={}", id); |
| | | return; |
| | | } |
| | | claimedIds.add(id); |
| | | } |
| | | processMergedInOut(group); |
| | | } catch (Exception e) { |
| | | log.warn("云仓上报同批合并异常:{}", e.getMessage()); |
| | | } finally { |
| | | for (Long id : claimedIds) { |
| | | cloudWmsNotifyLogService.clearSending(id); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private void processMergedInOut(List<CloudWmsNotifyLog> group) { |
| | | Date now = new Date(); |
| | | List<InOutResultReportParam> lines = new ArrayList<>(); |
| | | try { |
| | | for (CloudWmsNotifyLog row : group) { |
| | | lines.addAll(cloudWmsNotifyLogService.parseInOutLinesFromRequestBody(row.getRequestBody())); |
| | | } |
| | | } catch (IOException e) { |
| | | String msg = "反序列化失败: " + e.getMessage(); |
| | | for (CloudWmsNotifyLog row : group) { |
| | | int nextRetry = (row.getRetryCount() == null ? 0 : row.getRetryCount()) + 1; |
| | | setFailResult(row, row.getRequestBody(), msg, nextRetry, now, row.getMaxRetryCount()); |
| | | } |
| | | return; |
| | | } |
| | | if (lines.isEmpty()) { |
| | | return; |
| | | } |
| | | String mergedBody; |
| | | try { |
| | | mergedBody = objectMapper.writeValueAsString(new InOutResultBatchPayload().setLines(lines)); |
| | | } catch (JsonProcessingException e) { |
| | | for (CloudWmsNotifyLog row : group) { |
| | | int nextRetry = (row.getRetryCount() == null ? 0 : row.getRetryCount()) + 1; |
| | | setFailResult(row, row.getRequestBody(), "合并请求体序列化失败: " + e.getMessage(), nextRetry, now, row.getMaxRetryCount()); |
| | | } |
| | | return; |
| | | } |
| | | log.info("云仓上报开始(同单合并),ids={},requestBody={}", idsOf(group), mergedBody); |
| | | try { |
| | | Map<String, Object> res = cloudWmsReportService.reportInOutResults(lines); |
| | | for (CloudWmsNotifyLog row : group) { |
| | | int nextRetry = (row.getRetryCount() == null ? 0 : row.getRetryCount()) + 1; |
| | | updateAfterNotify(row, mergedBody, res, nextRetry, now, row.getMaxRetryCount()); |
| | | } |
| | | } catch (FeignException e) { |
| | | String responseBody = e.contentUTF8(); |
| | | String fullMsg = "status=" + e.status() + ",message=" + e.getMessage() |
| | | + (responseBody == null || responseBody.isEmpty() ? "" : ",responseBody=" + responseBody); |
| | | log.warn("云仓上报同批合并请求失败:{}", fullMsg); |
| | | for (CloudWmsNotifyLog row : group) { |
| | | int nextRetry = (row.getRetryCount() == null ? 0 : row.getRetryCount()) + 1; |
| | | setFailResult(row, mergedBody, "请求异常: " + fullMsg, nextRetry, now, row.getMaxRetryCount()); |
| | | } |
| | | } catch (Exception e) { |
| | | log.warn("云仓上报同批合并请求失败:{}", e.getMessage()); |
| | | for (CloudWmsNotifyLog row : group) { |
| | | int nextRetry = (row.getRetryCount() == null ? 0 : row.getRetryCount()) + 1; |
| | | setFailResult(row, mergedBody, "请求异常: " + e.getMessage(), nextRetry, now, row.getMaxRetryCount()); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private static List<Long> idsOf(List<CloudWmsNotifyLog> group) { |
| | | List<Long> ids = new ArrayList<>(group.size()); |
| | | for (CloudWmsNotifyLog row : group) { |
| | | ids.add(row.getId()); |
| | | } |
| | | return ids; |
| | | } |
| | | |
| | | private void safeProcessOne(CloudWmsNotifyLog logRecord) { |
| | | Long id = logRecord.getId(); |
| | | if (!cloudWmsNotifyLogService.tryClaimSending(id)) { |
| | | log.debug("云仓上报未抢到发送权 id={}", id); |
| | | return; |
| | | } |
| | | try { |
| | | processOne(logRecord); |
| | | } catch (Exception e) { |
| | | log.warn("云仓上报定时任务处理单条异常,id={},bizRef={}:{}", logRecord.getId(), logRecord.getBizRef(), e.getMessage()); |
| | | } finally { |
| | | cloudWmsNotifyLogService.clearSending(id); |
| | | } |
| | | } |
| | | |
| | | private boolean shouldProcess(CloudWmsNotifyLog logRecord, long nowMs) { |
| | | Integer maxRetry = logRecord.getMaxRetryCount(); |
| | | Integer intervalSeconds = logRecord.getRetryIntervalSeconds(); |
| | | if (maxRetry == null || intervalSeconds == null) { |
| | | log.warn("云仓上报待办跳过:重试参数缺失,id={},bizRef={},maxRetry={},intervalSeconds={}", |
| | | logRecord.getId(), logRecord.getBizRef(), maxRetry, intervalSeconds); |
| | | return false; |
| | | } |
| | | if (!isInfiniteRetry(maxRetry) |
| | | && logRecord.getRetryCount() != null |
| | | && logRecord.getRetryCount() >= maxRetry) { |
| | | log.info("云仓上报待办跳过:重试次数已达上限,id={},bizRef={},retryCount={},maxRetry={}", |
| | | logRecord.getId(), logRecord.getBizRef(), logRecord.getRetryCount(), maxRetry); |
| | | return false; |
| | | } |
| | | int effectiveIntervalSeconds = Math.max(0, intervalSeconds); |
| | | if (logRecord.getLastNotifyTime() != null) { |
| | | long elapsed = (nowMs - logRecord.getLastNotifyTime().getTime()) / 1000; |
| | | if (elapsed < effectiveIntervalSeconds) { |
| | | return false; |
| | | } |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | private void processOne(CloudWmsNotifyLog logRecord) { |
| | |
| | | Date now = new Date(); |
| | | int nextRetry = (logRecord.getRetryCount() == null ? 0 : logRecord.getRetryCount()) + 1; |
| | | int effectiveMaxRetry = logRecord.getMaxRetryCount(); |
| | | String rtInOut = cloudWmsNotifyLogService.getReportTypeInOutResult(); |
| | | String rtAdj = cloudWmsNotifyLogService.getReportTypeInventoryAdjust(); |
| | | log.info("云仓上报开始,id={},bizRef={},reportType={},attempt={},requestBody={}", |
| | | logRecord.getId(), logRecord.getBizRef(), reportType, nextRetry, requestBody); |
| | | |
| | | try { |
| | | if (cloudWmsNotifyLogService.getReportTypeInOutResult().equals(reportType)) { |
| | | if (rtInOut.equals(reportType)) { |
| | | JsonNode root = objectMapper.readTree(requestBody); |
| | | Map<String, Object> res; |
| | | if (root.has("lines") && root.get("lines").isArray()) { |
| | | InOutResultBatchPayload batch = objectMapper.readValue(requestBody, InOutResultBatchPayload.class); |
| | | res = cloudWmsReportService.reportInOutResults(batch.getLines()); |
| | | } else { |
| | | InOutResultReportParam param = objectMapper.readValue(requestBody, InOutResultReportParam.class); |
| | | Map<String, Object> res = cloudWmsReportService.reportInOutResult(param); |
| | | res = cloudWmsReportService.reportInOutResult(param); |
| | | } |
| | | updateAfterNotify(logRecord, requestBody, res, nextRetry, now, effectiveMaxRetry); |
| | | } else if (cloudWmsNotifyLogService.getReportTypeInventoryAdjust().equals(reportType)) { |
| | | } else if (rtAdj.equals(reportType)) { |
| | | InventoryAdjustReportParam param = objectMapper.readValue(requestBody, InventoryAdjustReportParam.class); |
| | | Map<String, Object> res = cloudWmsReportService.reportInventoryAdjust(param); |
| | | updateAfterNotify(logRecord, requestBody, res, nextRetry, now, effectiveMaxRetry); |
| | |
| | | logRecord.setLastNotifyTime(now); |
| | | logRecord.setRetryCount(nextRetry); |
| | | logRecord.setNotifyStatus(status); |
| | | logRecord.setSending(0); |
| | | logRecord.setUpdateTime(now); |
| | | cloudWmsNotifyLogService.updateById(logRecord); |
| | | log.info("云仓上报结束,id={},bizRef={},attempt={},notifyStatus={},responseBody={}", |
| | |
| | | logRecord.setLastResponseBody(truncateForStore(errorMsg)); |
| | | logRecord.setLastNotifyTime(now); |
| | | logRecord.setRetryCount(nextRetry); |
| | | // logRecord.setNotifyStatus(nextRetry >= effectiveMaxRetry ? cloudWmsNotifyLogService.getNotifyStatusFail() : cloudWmsNotifyLogService.getNotifyStatusPending()); |
| | | int status = !isInfiniteRetry(effectiveMaxRetry) && nextRetry >= effectiveMaxRetry |
| | | ? cloudWmsNotifyLogService.getNotifyStatusFail() |
| | | : cloudWmsNotifyLogService.getNotifyStatusPending(); |
| | | logRecord.setNotifyStatus(status); |
| | | logRecord.setSending(0); |
| | | logRecord.setUpdateTime(now); |
| | | cloudWmsNotifyLogService.updateById(logRecord); |
| | | log.warn("云仓上报失败,id={},bizRef={},attempt={},notifyStatus={},error={}", |
| | |
| | | package com.vincent.rsf.server.manager.service; |
| | | |
| | | import com.baomidou.mybatisplus.extension.service.IService; |
| | | import com.vincent.rsf.framework.common.R; |
| | | import com.vincent.rsf.server.api.controller.erp.params.InOutResultReportParam; |
| | | import com.vincent.rsf.server.manager.entity.CloudWmsNotifyLog; |
| | | |
| | | import java.io.IOException; |
| | | import java.util.List; |
| | | |
| | | /** 云仓上报待办 */ |
| | |
| | | |
| | | /** 通知状态:失败(系统配置优先,缺省 2) */ |
| | | int getNotifyStatusFail(); |
| | | |
| | | /** 入出库同单分组键(orderNo+入库/出库+仓),与调度内存合并一致 */ |
| | | String inOutMergeKeyFromRequestBody(String requestBody); |
| | | |
| | | /** 解析请求体为入出库行列表(单行 JSON 或 lines 数组) */ |
| | | List<InOutResultReportParam> parseInOutLinesFromRequestBody(String requestBody) throws IOException; |
| | | |
| | | /** 发送前占位,避免与合并并发;返回 false 表示已有实例在处理 */ |
| | | boolean tryClaimSending(Long id); |
| | | |
| | | void clearSending(Long id); |
| | | |
| | | /** sending=1 且 update_time 超时:补偿清零;启用 Redis 时还要求占位已失 */ |
| | | void recoverStaleSendingWhenRedisMiss(); |
| | | |
| | | /** manual 模式:按单号放行暂缓的入出库待办(send_hold=1→0) */ |
| | | R manualFlushToNotifyByOrderCode(String orderCode, boolean inbound); |
| | | } |
| | |
| | | package com.vincent.rsf.server.manager.service.impl; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.fasterxml.jackson.databind.JsonNode; |
| | | import com.fasterxml.jackson.databind.ObjectMapper; |
| | | import com.vincent.rsf.framework.common.R; |
| | | import com.vincent.rsf.server.api.controller.erp.params.InOutResultBatchPayload; |
| | | import com.vincent.rsf.server.api.controller.erp.params.InOutResultReportParam; |
| | | import com.vincent.rsf.server.manager.entity.AsnOrderLog; |
| | | import com.vincent.rsf.server.manager.entity.CloudWmsNotifyLog; |
| | |
| | | if (StringUtils.isBlank(row.getRequestBody())) { |
| | | continue; |
| | | } |
| | | InOutResultReportParam p; |
| | | try { |
| | | p = objectMapper.readValue(row.getRequestBody(), InOutResultReportParam.class); |
| | | } catch (Exception e) { |
| | | JsonNode root = objectMapper.readTree(row.getRequestBody()); |
| | | if (root.has("lines") && root.get("lines").isArray() && root.get("lines").size() > 0) { |
| | | InOutResultBatchPayload batch = objectMapper.readValue(row.getRequestBody(), InOutResultBatchPayload.class); |
| | | if (batch.getLines() == null || batch.getLines().isEmpty()) { |
| | | continue; |
| | | } |
| | | InOutResultReportParam first = batch.getLines().get(0); |
| | | if (!code.equals(first.getOrderNo()) || !matchOrderType(orderLog.getType(), first.getInbound())) { |
| | | continue; |
| | | } |
| | | latestByLine.putIfAbsent("batch_" + row.getId(), row); |
| | | continue; |
| | | } |
| | | InOutResultReportParam p = objectMapper.readValue(row.getRequestBody(), InOutResultReportParam.class); |
| | | if (p == null || !code.equals(p.getOrderNo())) { |
| | | continue; |
| | | } |
| | |
| | | } |
| | | String sig = lineSignature(p); |
| | | latestByLine.putIfAbsent(sig, row); |
| | | } catch (Exception e) { |
| | | continue; |
| | | } |
| | | } |
| | | if (latestByLine.isEmpty()) { |
| | | return R.error("未找到该单号对应的云仓入出库上报记录,无法重发"); |
| | |
| | | .setRetryCount(0) |
| | | .setBizRef("manualResend,asnOrderLogId=" + asnOrderLogId + ",fromNotifyLogId=" + src.getId() + ",orderNo=" + code) |
| | | .setCreateTime(now) |
| | | .setUpdateTime(now); |
| | | .setUpdateTime(now) |
| | | .setSendHold(0) |
| | | .setSending(0); |
| | | if (StringUtils.isNotBlank(src.getSourceOrderNo())) { |
| | | copy.setSourceOrderNo(src.getSourceOrderNo()) |
| | | .setInboundFlag(src.getInboundFlag()) |
| | | .setWareHouseCode(src.getWareHouseCode()); |
| | | } else { |
| | | fillInOutRoutingFromBody(copy, src.getRequestBody()); |
| | | } |
| | | cloudWmsNotifyLogService.fillFromConfig(copy); |
| | | if (cloudWmsNotifyLogService.save(copy)) { |
| | | n++; |
| | |
| | | return R.ok("已加入云仓重发队列 " + n + " 条,将由定时任务发送").add(n); |
| | | } |
| | | |
| | | private void fillInOutRoutingFromBody(CloudWmsNotifyLog copy, String body) { |
| | | if (StringUtils.isBlank(body)) { |
| | | return; |
| | | } |
| | | try { |
| | | JsonNode root = objectMapper.readTree(body); |
| | | if (root.has("lines") && root.get("lines").isArray() && root.get("lines").size() > 0) { |
| | | InOutResultReportParam first = objectMapper.treeToValue(root.get("lines").get(0), InOutResultReportParam.class); |
| | | if (first != null) { |
| | | boolean inb = first.getInbound() == null || Boolean.TRUE.equals(first.getInbound()); |
| | | copy.setSourceOrderNo(first.getOrderNo()) |
| | | .setInboundFlag(inb ? 1 : 0) |
| | | .setWareHouseCode(first.getWareHouseId()); |
| | | } |
| | | return; |
| | | } |
| | | InOutResultReportParam p = objectMapper.readValue(body, InOutResultReportParam.class); |
| | | if (p != null) { |
| | | boolean inb = p.getInbound() == null || Boolean.TRUE.equals(p.getInbound()); |
| | | copy.setSourceOrderNo(p.getOrderNo()) |
| | | .setInboundFlag(inb ? 1 : 0) |
| | | .setWareHouseCode(p.getWareHouseId()); |
| | | } |
| | | } catch (Exception ignored) { |
| | | } |
| | | } |
| | | |
| | | private static boolean matchOrderType(String asnType, Boolean inbound) { |
| | | if (StringUtils.isBlank(asnType)) { |
| | | return true; |
| | |
| | | package com.vincent.rsf.server.manager.service.impl; |
| | | |
| | | import com.vincent.rsf.framework.common.R; |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import com.fasterxml.jackson.databind.JsonNode; |
| | | import com.fasterxml.jackson.databind.ObjectMapper; |
| | | import com.vincent.rsf.server.api.controller.erp.params.InOutResultReportParam; |
| | | import com.vincent.rsf.server.common.service.RedisService; |
| | | import com.vincent.rsf.server.manager.entity.CloudWmsNotifyLog; |
| | | import com.vincent.rsf.server.manager.mapper.CloudWmsNotifyLogMapper; |
| | | import com.vincent.rsf.server.manager.service.CloudWmsNotifyLogService; |
| | | import com.vincent.rsf.server.system.constant.GlobalConfigCode; |
| | | import com.vincent.rsf.server.system.entity.Config; |
| | | import com.vincent.rsf.server.system.service.ConfigService; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.commons.lang3.StringUtils; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | |
| | | import java.io.IOException; |
| | | import java.util.Date; |
| | | import java.util.ArrayList; |
| | | import java.util.Collections; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | @Slf4j |
| | | @Service |
| | | public class CloudWmsNotifyLogServiceImpl extends ServiceImpl<CloudWmsNotifyLogMapper, CloudWmsNotifyLog> implements CloudWmsNotifyLogService { |
| | | |
| | | /** 单条待办「正在上报」Redis 占位秒数(SET NX EX) */ |
| | | private static final int CLOUD_WMS_NOTIFY_SENDING_REDIS_TTL_SECONDS = 120; |
| | | /** sending=1 但 Redis 无占位:update_time 早于此时长(分钟)则补偿清零 */ |
| | | private static final int STALE_SENDING_RECOVER_AFTER_MINUTES = 6; |
| | | |
| | | @Autowired |
| | | private ConfigService configService; |
| | | @Autowired |
| | | private ObjectMapper objectMapper; |
| | | @Autowired(required = false) |
| | | private RedisService redisService; |
| | | |
| | | private static final String CLOUD_WMS_REDIS_FLAG = "cloudwms"; |
| | | |
| | | private boolean useSendingRedis() { |
| | | return redisService != null |
| | | && Boolean.TRUE.equals(redisService.initialize) |
| | | && CLOUD_WMS_NOTIFY_SENDING_REDIS_TTL_SECONDS > 0; |
| | | } |
| | | |
| | | private static String sendingRedisSubKey(Long id) { |
| | | return "sending." + id; |
| | | } |
| | | |
| | | /** Redis 占位存在且未过期时视为正在上报 */ |
| | | private boolean isSendingHeldInRedis(Long id) { |
| | | if (id == null || !useSendingRedis()) { |
| | | return false; |
| | | } |
| | | try { |
| | | String v = redisService.getValue(CLOUD_WMS_REDIS_FLAG, sendingRedisSubKey(id)); |
| | | return StringUtils.isNotBlank(v); |
| | | } catch (Exception e) { |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public List<CloudWmsNotifyLog> listPending(int limit, int maxRetry) { |
| | | LambdaQueryWrapper<CloudWmsNotifyLog> wrapper = new LambdaQueryWrapper<CloudWmsNotifyLog>() |
| | | // 仅查询数据库配置状态:待通知 + 失败(可重试) |
| | | .in(CloudWmsNotifyLog::getNotifyStatus, getNotifyStatusPending(), getNotifyStatusFail()) |
| | | .apply("(send_hold IS NULL OR send_hold = 0)") |
| | | // 仅查询可重试数据:无限重试、未配置上限、或未达到上限 |
| | | .apply("(max_retry_count IS NULL OR max_retry_count = -1 OR retry_count < max_retry_count)") |
| | | // 仅查询已到重试时间的数据,避免前 50 条未到间隔导致后续记录长期饥饿 |
| | | .apply("(last_notify_time IS NULL OR retry_interval_seconds IS NULL OR retry_interval_seconds <= 0 OR TIMESTAMPDIFF(SECOND, last_notify_time, NOW()) >= retry_interval_seconds)") |
| | | //缺重试参数的不进入待发送列表 |
| | | .isNotNull(CloudWmsNotifyLog::getMaxRetryCount) |
| | | .isNotNull(CloudWmsNotifyLog::getRetryIntervalSeconds) |
| | | .orderByAsc(CloudWmsNotifyLog::getLastNotifyTime) |
| | | .orderByAsc(CloudWmsNotifyLog::getId); |
| | | if (maxRetry >= 0) { |
| | |
| | | } |
| | | Page<CloudWmsNotifyLog> page = new Page<>(1, Math.max(1, limit)); |
| | | return page(page, wrapper).getRecords(); |
| | | } |
| | | |
| | | @Override |
| | | public boolean tryClaimSending(Long id) { |
| | | if (id == null) { |
| | | return false; |
| | | } |
| | | boolean useRedis = useSendingRedis(); |
| | | if (useRedis) { |
| | | boolean got = redisService.trySetStringNxEx(CLOUD_WMS_REDIS_FLAG, sendingRedisSubKey(id), "1", |
| | | CLOUD_WMS_NOTIFY_SENDING_REDIS_TTL_SECONDS); |
| | | if (!got) { |
| | | CloudWmsNotifyLog cur = getById(id); |
| | | if (cur != null && (cur.getSending() == null || cur.getSending() == 0)) { |
| | | redisService.deleteValue(CLOUD_WMS_REDIS_FLAG, sendingRedisSubKey(id)); |
| | | got = redisService.trySetStringNxEx(CLOUD_WMS_REDIS_FLAG, sendingRedisSubKey(id), "1", |
| | | CLOUD_WMS_NOTIFY_SENDING_REDIS_TTL_SECONDS); |
| | | } |
| | | if (!got) { |
| | | return false; |
| | | } |
| | | } |
| | | } |
| | | LambdaUpdateWrapper<CloudWmsNotifyLog> u = new LambdaUpdateWrapper<>(); |
| | | u.eq(CloudWmsNotifyLog::getId, id) |
| | | .set(CloudWmsNotifyLog::getSending, 1) |
| | | .set(CloudWmsNotifyLog::getUpdateTime, new Date()); |
| | | if (!useRedis) { |
| | | u.and(w -> w.isNull(CloudWmsNotifyLog::getSending).or().eq(CloudWmsNotifyLog::getSending, 0)); |
| | | } |
| | | boolean ok = update(u); |
| | | if (!ok && useRedis) { |
| | | try { |
| | | redisService.deleteValue(CLOUD_WMS_REDIS_FLAG, sendingRedisSubKey(id)); |
| | | } catch (Exception ignored) { |
| | | } |
| | | } |
| | | return ok; |
| | | } |
| | | |
| | | @Override |
| | | public void clearSending(Long id) { |
| | | if (id == null) { |
| | | return; |
| | | } |
| | | if (useSendingRedis()) { |
| | | try { |
| | | redisService.deleteValue(CLOUD_WMS_REDIS_FLAG, sendingRedisSubKey(id)); |
| | | } catch (Exception e) { |
| | | log.debug("云仓上报 clearSending Redis id={}", id, e); |
| | | } |
| | | } |
| | | LambdaUpdateWrapper<CloudWmsNotifyLog> u = new LambdaUpdateWrapper<>(); |
| | | u.eq(CloudWmsNotifyLog::getId, id) |
| | | .set(CloudWmsNotifyLog::getSending, 0) |
| | | .set(CloudWmsNotifyLog::getUpdateTime, new Date()); |
| | | update(u); |
| | | } |
| | | |
| | | @Override |
| | | public void recoverStaleSendingWhenRedisMiss() { |
| | | Date threshold = new Date(System.currentTimeMillis() - STALE_SENDING_RECOVER_AFTER_MINUTES * 60_000L); |
| | | List<CloudWmsNotifyLog> rows = list(new LambdaQueryWrapper<CloudWmsNotifyLog>() |
| | | .eq(CloudWmsNotifyLog::getSending, 1) |
| | | .isNotNull(CloudWmsNotifyLog::getUpdateTime) |
| | | .lt(CloudWmsNotifyLog::getUpdateTime, threshold) |
| | | .last("LIMIT 500")); |
| | | if (rows.isEmpty()) { |
| | | return; |
| | | } |
| | | Date now = new Date(); |
| | | int cleared = 0; |
| | | for (CloudWmsNotifyLog row : rows) { |
| | | Long id = row.getId(); |
| | | if (id == null) { |
| | | continue; |
| | | } |
| | | if (useSendingRedis() && isSendingHeldInRedis(id)) { |
| | | continue; |
| | | } |
| | | LambdaUpdateWrapper<CloudWmsNotifyLog> u = new LambdaUpdateWrapper<>(); |
| | | u.eq(CloudWmsNotifyLog::getId, id) |
| | | .eq(CloudWmsNotifyLog::getSending, 1) |
| | | .lt(CloudWmsNotifyLog::getUpdateTime, threshold) |
| | | .set(CloudWmsNotifyLog::getSending, 0) |
| | | .set(CloudWmsNotifyLog::getUpdateTime, now); |
| | | if (update(u)) { |
| | | cleared++; |
| | | } |
| | | } |
| | | if (cleared > 0) { |
| | | log.info("云仓待办 sending 补偿清零 {} 条", cleared); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public R manualFlushToNotifyByOrderCode(String orderCode, boolean inbound) { |
| | | if (StringUtils.isBlank(orderCode)) { |
| | | return R.error("单号不能为空"); |
| | | } |
| | | int flag = inbound ? 1 : 0; |
| | | Date now = new Date(); |
| | | LambdaUpdateWrapper<CloudWmsNotifyLog> u = new LambdaUpdateWrapper<>(); |
| | | u.eq(CloudWmsNotifyLog::getReportType, getReportTypeInOutResult()) |
| | | .eq(CloudWmsNotifyLog::getSourceOrderNo, orderCode.trim()) |
| | | .eq(CloudWmsNotifyLog::getInboundFlag, flag) |
| | | .eq(CloudWmsNotifyLog::getSendHold, 1) |
| | | .in(CloudWmsNotifyLog::getNotifyStatus, getNotifyStatusPending(), getNotifyStatusFail()) |
| | | .set(CloudWmsNotifyLog::getSendHold, 0) |
| | | .set(CloudWmsNotifyLog::getUpdateTime, now); |
| | | int n = getBaseMapper().update(null, u); |
| | | if (n <= 0) { |
| | | return R.error("当前无待放行的入出库待办(请确认 manual 模式、单号与入/出库类型一致)"); |
| | | } |
| | | return R.ok("已放行云仓上报待办 " + n + " 条").add(n); |
| | | } |
| | | |
| | | @Override |
| | |
| | | return getConfigInt(GlobalConfigCode.CLOUD_WMS_NOTIFY_STATUS_FAIL, CloudWmsNotifyLog.NOTIFY_STATUS_FAIL); |
| | | } |
| | | |
| | | private String getConfigString(String flag, String defaultVal) { |
| | | Config c = configService.getOne(new LambdaQueryWrapper<Config>() |
| | | .eq(Config::getFlag, flag) |
| | | .orderByDesc(Config::getId) |
| | | .last("LIMIT 1")); |
| | | if (c != null && c.getVal() != null && !c.getVal().isEmpty()) { |
| | | return c.getVal().trim(); |
| | | private String getConfigValTrimmed(String flag) { |
| | | Config c = configService.getCachedOrLoad(flag); |
| | | if (c == null || c.getVal() == null) { |
| | | return null; |
| | | } |
| | | return defaultVal; |
| | | String v = c.getVal().trim(); |
| | | return v.isEmpty() ? null : v; |
| | | } |
| | | |
| | | /** 与实体常量搭配:仅当库/缓存无有效 val 时用常量 */ |
| | | private String getConfigString(String flag, String defaultVal) { |
| | | String v = getConfigValTrimmed(flag); |
| | | return v != null ? v : defaultVal; |
| | | } |
| | | |
| | | private int getConfigInt(String flag, int defaultVal) { |
| | | Integer v = getConfigInt(flag); |
| | | return v != null ? v : defaultVal; |
| | | Integer n = getConfigIntOrNull(flag); |
| | | return n != null ? n : defaultVal; |
| | | } |
| | | |
| | | private Integer getConfigIntOrNull(String flag) { |
| | | String v = getConfigValTrimmed(flag); |
| | | if (v == null) { |
| | | return null; |
| | | } |
| | | try { |
| | | return Integer.parseInt(v); |
| | | } catch (NumberFormatException e) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void fillFromConfig(CloudWmsNotifyLog log) { |
| | | Integer maxRetry = getConfigInt(GlobalConfigCode.CLOUD_WMS_NOTIFY_MAX_RETRY); |
| | | Integer interval = getConfigInt(GlobalConfigCode.CLOUD_WMS_NOTIFY_RETRY_INTERVAL_SECONDS); |
| | | log.setNotifyStatus(getNotifyStatusPending()); |
| | | log.setMaxRetryCount(maxRetry); |
| | | log.setRetryIntervalSeconds(interval); |
| | | log.setMaxRetryCount(getConfigIntOrNull(GlobalConfigCode.CLOUD_WMS_NOTIFY_MAX_RETRY)); |
| | | log.setRetryIntervalSeconds(getConfigIntOrNull(GlobalConfigCode.CLOUD_WMS_NOTIFY_RETRY_INTERVAL_SECONDS)); |
| | | } |
| | | |
| | | /** 返回 null 表示未配置或解析失败 */ |
| | | private Integer getConfigInt(String flag) { |
| | | try { |
| | | Config c = configService.getOne(new LambdaQueryWrapper<Config>() |
| | | .eq(Config::getFlag, flag) |
| | | .orderByDesc(Config::getId) |
| | | .last("LIMIT 1")); |
| | | if (c != null && c.getVal() != null && !c.getVal().isEmpty()) { |
| | | return Integer.parseInt(c.getVal().trim()); |
| | | @Override |
| | | public String inOutMergeKeyFromRequestBody(String requestBody) { |
| | | return mergeKeyFromBody(requestBody); |
| | | } |
| | | } catch (Exception ignored) { |
| | | |
| | | @Override |
| | | public List<InOutResultReportParam> parseInOutLinesFromRequestBody(String requestBody) throws IOException { |
| | | return extractInOutLines(requestBody); |
| | | } |
| | | |
| | | private String mergeKeyFromBody(String body) { |
| | | if (StringUtils.isBlank(body)) { |
| | | return null; |
| | | } |
| | | try { |
| | | JsonNode root = objectMapper.readTree(body); |
| | | if (root.has("lines") && root.get("lines").isArray() && root.get("lines").size() > 0) { |
| | | return null; |
| | | } |
| | | JsonNode first = root; |
| | | String orderNo = textNode(first, "orderNo"); |
| | | if (StringUtils.isBlank(orderNo)) { |
| | | return null; |
| | | } |
| | | String wh = textNode(first, "wareHouseId"); |
| | | boolean inbound = !first.has("inbound") || first.get("inbound").isNull() || first.get("inbound").asBoolean(); |
| | | return orderNo + "\t" + inbound + "\t" + StringUtils.defaultString(wh); |
| | | } catch (Exception e) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private static String textNode(JsonNode n, String field) { |
| | | if (n == null || !n.has(field) || n.get(field).isNull()) { |
| | | return null; |
| | | } |
| | | return n.get(field).asText(); |
| | | } |
| | | |
| | | private List<InOutResultReportParam> extractInOutLines(String body) throws java.io.IOException { |
| | | JsonNode root = objectMapper.readTree(body); |
| | | if (root.has("lines") && root.get("lines").isArray()) { |
| | | List<InOutResultReportParam> list = new ArrayList<>(); |
| | | for (JsonNode n : root.get("lines")) { |
| | | list.add(objectMapper.treeToValue(n, InOutResultReportParam.class)); |
| | | } |
| | | return list; |
| | | } |
| | | return Collections.singletonList(objectMapper.readValue(body, InOutResultReportParam.class)); |
| | | } |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.server.manager.service.impl; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
| | | import com.vincent.rsf.server.manager.entity.Task; |
| | | import com.vincent.rsf.server.manager.enums.TaskStsType; |
| | | import com.vincent.rsf.server.manager.mapper.TaskMapper; |
| | | import org.apache.commons.lang3.StringUtils; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | /** |
| | | * 任务明细 plat_order_code 对应云仓通知单号时,是否仍有执行中任务 |
| | | */ |
| | | @Component |
| | | public class CloudWmsOrderTaskRunningHelper { |
| | | |
| | | @Autowired |
| | | private TaskMapper taskMapper; |
| | | |
| | | public boolean hasRunningTasksForPlatOrder(String platOrderCode) { |
| | | if (StringUtils.isBlank(platOrderCode)) { |
| | | return false; |
| | | } |
| | | QueryWrapper<Task> qw = new QueryWrapper<>(); |
| | | qw.eq("deleted", 0); |
| | | qw.apply("EXISTS (SELECT 1 FROM man_task_item ti WHERE ti.task_id = man_task.id AND ti.deleted = 0 AND ti.plat_order_code = {0})", platOrderCode); |
| | | qw.and(w -> w |
| | | .nested(n -> n.lt("task_type", 100).lt("task_status", TaskStsType.COMPLETE_IN.id)) |
| | | .or(n -> n.ge("task_type", 101).lt("task_status", TaskStsType.COMPLETE_OUT.id))); |
| | | Integer cnt = taskMapper.selectCount(qw); |
| | | return cnt != null && cnt > 0; |
| | | } |
| | | } |
| | |
| | | import com.vincent.rsf.framework.common.R; |
| | | import com.vincent.rsf.framework.exception.CoolException; |
| | | import com.vincent.rsf.server.api.utils.LocUtils; |
| | | import com.vincent.rsf.server.manager.constant.CloudWmsInoutReportMode; |
| | | import com.vincent.rsf.server.manager.controller.params.GenerateTaskParams; |
| | | import com.vincent.rsf.server.manager.entity.CloudWmsNotifyLog; |
| | | import com.vincent.rsf.server.manager.entity.*; |
| | |
| | | log.info("入/出库结果上报待办跳过:无云仓来源单据,taskId={}", task.getId()); |
| | | return; |
| | | } |
| | | Set<Long> orderIdSet = taskItems.stream() |
| | | .filter(Objects::nonNull) |
| | | .map(TaskItem::getOrderId) |
| | | .filter(Objects::nonNull) |
| | | .collect(Collectors.toSet()); |
| | | Map<Long, WkOrder> orderById = new HashMap<>(); |
| | | if (!orderIdSet.isEmpty()) { |
| | | for (WkOrder o : asnOrderService.listByIds(orderIdSet)) { |
| | | if (o != null && o.getId() != null) { |
| | | orderById.put(o.getId(), o); |
| | | } |
| | | } |
| | | } |
| | | Set<String> orderCodeSet = new HashSet<>(); |
| | | for (TaskItem ti : taskItems) { |
| | | if (ti == null) { |
| | | continue; |
| | | } |
| | | String on = isInbound && ti.getSource() != null |
| | | ? sourceToOrderNo.get(ti.getSource()) |
| | | : (ti.getPlatOrderCode() != null ? ti.getPlatOrderCode() : ti.getPlatWorkCode()); |
| | | if (on == null && isInbound) { |
| | | on = ti.getPlatOrderCode() != null ? ti.getPlatOrderCode() : ti.getPlatWorkCode(); |
| | | } |
| | | if (StringUtils.isNotBlank(on)) { |
| | | orderCodeSet.add(on); |
| | | } |
| | | } |
| | | Map<String, WkOrder> orderByCode = new HashMap<>(); |
| | | if (!orderCodeSet.isEmpty()) { |
| | | for (WkOrder o : asnOrderService.list(new LambdaQueryWrapper<WkOrder>().in(WkOrder::getCode, orderCodeSet))) { |
| | | if (o != null && StringUtils.isNotBlank(o.getCode())) { |
| | | orderByCode.put(o.getCode(), o); |
| | | } |
| | | } |
| | | } |
| | | ObjectMapper om = new ObjectMapper(); |
| | | Date now = new Date(); |
| | | Map<String, List<InOutResultReportParam>> byOrder = new LinkedHashMap<>(); |
| | | for (TaskItem item : taskItems) { |
| | | if (item == null) { |
| | | continue; |
| | |
| | | if (orderNo == null || item.getMatnrCode() == null) { |
| | | continue; |
| | | } |
| | | WkOrder asnOrder = null; |
| | | if (item.getOrderId() != null) { |
| | | asnOrder = orderById.get(item.getOrderId()); |
| | | } |
| | | if (asnOrder == null) { |
| | | asnOrder = orderByCode.get(orderNo); |
| | | } |
| | | InOutResultReportParam param = new InOutResultReportParam() |
| | | .setOrderNo(orderNo) |
| | | .setPlanNo(item.getPlatWorkCode()) |
| | |
| | | .setUnitNo(item.getUnit()) |
| | | .setLineId(item.getPlatItemId()) |
| | | .setWareHouseId(wareHouseId) |
| | | .setDocWarehouseNo(asnOrder != null ? asnOrder.getDocTaskWarehouseNo() : null) |
| | | .setOrgNo(asnOrder != null ? asnOrder.getDocOrgNo() : null) |
| | | .setInWarehouseNo(isInbound && asnOrder != null ? asnOrder.getDocInWarehouseNo() : null) |
| | | .setOutWarehouseNo(!isInbound && asnOrder != null ? asnOrder.getDocOutWarehouseNo() : null) |
| | | .setLocId(locId) |
| | | .setMatNr(item.getMatnrCode()) |
| | | .setQty(item.getAnfme() != null ? String.valueOf(item.getAnfme()) : "0") |
| | | .setBatch(item.getBatch()) |
| | | .setInbound(isInbound) |
| | | .setBarcode(task.getBarcode()); |
| | | byOrder.computeIfAbsent(orderNo, k -> new ArrayList<>()).add(param); |
| | | } |
| | | String mode = resolveCloudWmsInoutReportMode(); |
| | | boolean sendHold = CloudWmsInoutReportMode.MANUAL.equals(mode) || CloudWmsInoutReportMode.WAIT_ORDER.equals(mode); |
| | | for (Map.Entry<String, List<InOutResultReportParam>> e : byOrder.entrySet()) { |
| | | String orderNo = e.getKey(); |
| | | for (InOutResultReportParam param : e.getValue()) { |
| | | try { |
| | | String requestBody = om.writeValueAsString(param); |
| | | CloudWmsNotifyLog notifyLog = new CloudWmsNotifyLog() |
| | | .setReportType(cloudWmsNotifyLogService.getReportTypeInOutResult()) |
| | | .setRequestBody(requestBody) |
| | | .setNotifyStatus(cloudWmsNotifyLogService.getNotifyStatusPending()) |
| | | .setRetryCount(0) |
| | | .setBizRef("taskId=" + task.getId() + ",orderNo=" + orderNo) |
| | | .setCreateTime(now) |
| | | .setUpdateTime(now); |
| | | .setUpdateTime(now) |
| | | .setSourceOrderNo(orderNo) |
| | | .setInboundFlag(isInbound ? 1 : 0) |
| | | .setWareHouseCode(wareHouseId) |
| | | .setSendHold(sendHold ? 1 : 0) |
| | | .setSending(0); |
| | | cloudWmsNotifyLogService.fillFromConfig(notifyLog); |
| | | cloudWmsNotifyLogService.save(notifyLog); |
| | | } catch (JsonProcessingException e) { |
| | | log.warn("入/出库结果上报待办落库失败(不影响主流程),taskId={},orderNo={}:{}", task.getId(), orderNo, e.getMessage()); |
| | | } catch (JsonProcessingException ex) { |
| | | log.warn("入/出库结果上报待办落库失败(不影响主流程),taskId={},orderNo={}:{}", task.getId(), orderNo, ex.getMessage()); |
| | | } |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | |
| | | return StringUtils.isNotBlank(item.getPlatOrderCode()) |
| | | || StringUtils.isNotBlank(item.getPlatWorkCode()); |
| | | } |
| | | |
| | | /** sys_config CLOUD_WMS_INOUT_REPORT_MODE:immediate / wait_order / manual */ |
| | | private String resolveCloudWmsInoutReportMode() { |
| | | try { |
| | | Config cfg = configService.getCachedOrLoad(GlobalConfigCode.CLOUD_WMS_INOUT_REPORT_MODE); |
| | | if (cfg != null && StringUtils.isNotBlank(cfg.getVal())) { |
| | | return cfg.getVal().trim().toLowerCase(); |
| | | } |
| | | } catch (Exception ignored) { |
| | | } |
| | | return CloudWmsInoutReportMode.IMMEDIATE; |
| | | } |
| | | } |
| | |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | /** |
| | | * sys_config 经 Redis 缓存时的过期时间(非永久,到期后读库刷新)。 |
| | | * sys_config 写入 Redis 时的过期秒数,由 {@code config.cache.redis-ttl-seconds} 配置。 |
| | | */ |
| | | @Data |
| | | @Component |
| | | @ConfigurationProperties(prefix = "config.cache") |
| | | public class ConfigCacheProperties { |
| | | |
| | | /** Redis 中 SYS_CONFIG 条目的过期秒数,须 >=1(默认 4 小时) */ |
| | | private int redisTtlSeconds = 14400; |
| | | private int redisTtlSeconds; |
| | | } |
| | |
| | | /** 云仓通知入库体最大长度(last_request_body / last_response_body,默认 2000) */ |
| | | public final static String CLOUD_WMS_NOTIFY_STORE_BODY_MAX_CHARS = "CLOUD_WMS_NOTIFY_STORE_BODY_MAX_CHARS"; |
| | | |
| | | /** |
| | | * 云仓入出库回馈模式(sys_config.val,type=3):immediate 立即待办; |
| | | * wait_order 实绩落待办且暂缓发送,空闲/整单结束后放行;manual 暂缓发送,由通知单按钮放行。 |
| | | */ |
| | | public final static String CLOUD_WMS_INOUT_REPORT_MODE = "CLOUD_WMS_INOUT_REPORT_MODE"; |
| | | |
| | | /** wait_order:超过该秒数无新实绩更新则放行暂缓的待办(可与整单结束择先触发) */ |
| | | public final static String CLOUD_WMS_INOUT_AGG_IDLE_SECONDS = "CLOUD_WMS_INOUT_AGG_IDLE_SECONDS"; |
| | | |
| | | /** 同订单已无执行中任务后,再等待该秒数再整批上报,避免连续完成任务时拆单 */ |
| | | public final static String CLOUD_WMS_INOUT_AGG_COMPLETE_DEBOUNCE_SECONDS = "CLOUD_WMS_INOUT_AGG_COMPLETE_DEBOUNCE_SECONDS"; |
| | | |
| | | /** 指定物料自动全板出库:物料编码(val=物料编码时启用按物料自动生成全板出库单) */ |
| | | public final static String AUTO_FULL_OUT_MATNR_CODE = "AUTO_FULL_OUT_MATNR_CODE"; |
| | | /** 是否启用:有库存时自动生成全板出库单 */ |
| | |
| | | public interface ConfigService extends IService<Config> { |
| | | |
| | | /** |
| | | * 优先 JVM 内全局缓存(启动预载 + 后台增改删时维护),未命中或缓存已失效再查库并回填。 |
| | | * 若启用 Redis:先读带 TTL 的副本,过期或缺失则读库并以 setex 回写;不使用永久 key。 |
| | | * Redis 可用时:先读 SYS_CONFIG 带 TTL 的条目;未命中或无效则读库,命中后尝试 setex 回写(写失败仍返回库里的值)。 |
| | | * 无 Redis 时:用进程内 CONFIG_CACHE,未命中再读库。 |
| | | */ |
| | | Config getCachedOrLoad(String flag); |
| | | |
| | |
| | | } |
| | | |
| | | private void tryRedisSetexConfig(String flag, Config loaded) { |
| | | if (!redisReady() || loaded == null) { |
| | | return; |
| | | } |
| | | int ttl = configCacheProperties.getRedisTtlSeconds(); |
| | | if (ttl <= 0) { |
| | | log.warn("sys_config Redis setex 已跳过:config.cache.redis-ttl-seconds 须为正数,flag={}", flag); |
| | | return; |
| | | } |
| | | try { |
| | | int ttl = Math.max(1, configCacheProperties.getRedisTtlSeconds()); |
| | | redisService.set(REDIS_FLAG_SYS_CONFIG, flag, loaded, ttl); |
| | | } catch (Exception e) { |
| | | log.warn("sys_config Redis setex flag={}", flag, e); |
| | |
| | | # Feign 调用云仓时的根地址。本机模拟:http://127.0.0.1:8086/rsf-server(CloudWmsMockController:ICusStockService stockIn/Out/TransferCompleted) |
| | | # base-url: http://127.0.0.1:8086/rsf-server |
| | | base-url: http://192.168.10.108:8180 |
| | | # 鼎捷 DAP ilcwmsplus 完成反馈(9.1/9.2 组包用) |
| | | dap: |
| | | org-no: "" |
| | | doc-type-in: "" |
| | | doc-type-out: "" |
| | | doc-type-adj: "" |
| | | unit-no: PCS |
| | | #接口明细(质检等;Feign 已固定 ICusStockService stockInCompleted、stockOutCompleted;调拨 changeType=3 为 stockTransferCompleted) |
| | | api: |
| | | notify-inspect: /report/inspect |
| | |
| | | openfeign: |
| | | circuitbreaker: |
| | | enabled: true # Feign 调用失败时走 Fallback,在 Feign 内统一返回错误响应 |
| | | client: |
| | | config: |
| | | cloudWmsErp: |
| | | connectTimeout: 5000 |
| | | readTimeout: 10000 |
| | | mvc: |
| | | static-path-pattern: /** |
| | | path match: |
| | |
| | | # Feign 调用云仓时的根地址。本机模拟:http://127.0.0.1:8086/rsf-server(CloudWmsMockController:ICusStockService stockIn/Out/TransferCompleted) |
| | | # base-url: http://127.0.0.1:8086/rsf-server |
| | | base-url: http://192.168.10.108:8180 |
| | | # 鼎捷 DAP ilcwmsplus 完成反馈(9.1/9.2 组包用) |
| | | dap: |
| | | org-no: "1" |
| | | #接口明细(质检等;下方 stock-in/out-completed-path 与 CloudWmsErpFeignClient 一致;调拨 changeType=3 为 stockTransferCompleted) |
| | | api: |
| | | notify-inspect: /report/inspect |
| | |
| | | system-version: @pom.version@ |
| | | system-mode: OFFLINE |
| | | cache: |
| | | # sys_config 写入 Redis 的过期秒数(setex,不用永久 key);到期后下次读取从库刷新(4 小时) |
| | | redis-ttl-seconds: 14400 |
| | | # sys_config 写入 Redis 的过期秒数(setex);到期后下次 getCachedOrLoad 从库刷新再回写 |
| | | redis-ttl-seconds: 3600 |
| | | token-key: KUHSMcYQ4lePt3r6bckz0P13cBJyoonYqInThvQlUnbsFCIcCcZZAbWZ6UNFztYNYPhGdy6eyb8WdIz8FU2Cz396TyTJk3NI2rtXMHBOehRb4WWJ4MdYVVg2oWPyqRQ2 |
| | | super-username: root |
| | | code-length: 6 |