去除mqtt功能

修改查询接口
This commit is contained in:
2026-01-08 11:02:34 +08:00
parent f3d993b33a
commit 5d227b7b6b
18 changed files with 329 additions and 726 deletions

View File

@@ -1,182 +0,0 @@
package com.zg.project.wisdom.config;
import com.zg.project.wisdom.mqtt.dispatcher.MqttMessageDispatcher;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.*;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
/**
* MQTT 客户端初始化配置类
*
* 作用说明:
* 1. Spring Boot 启动时自动创建 MQTT 客户端
* 2. 连接 EMQX Broker
* 3. 订阅 application.yml 中配置的 Topic支持通配符
* 4. 接收传感器上报的数据
*
* 说明:
* - 同一个 MQTT Client 同时支持「订阅(接收)」和「发布(下发指令)」
* - 后端一般只需要一个 Client
*/
@Slf4j
@Configuration
public class MqttClientConfig {
/**
* MQTT 客户端实例
* 由本配置类统一维护
*/
private MqttClient mqttClient;
@Resource
private MqttMessageDispatcher mqttMessageDispatcher;
/**
* 创建 MQTT Client Bean
*
* @param props MQTT 配置(来自 application.yml
*/
@Bean
public MqttClient mqttClient(MqttProperties props) throws MqttException {
// 如果未开启 MQTT 功能,直接不初始化
// 注意:此时 mqttClient Bean 为 null
if (!props.isEnabled()) {
log.warn("[MQTT] mqtt.enabled=falseMQTT 功能未启用");
return null;
}
// 1. 创建 MQTT 客户端
mqttClient = new MqttClient(
props.getBroker(),
props.getClientId(),
new MemoryPersistence()
);
// 2. MQTT 连接参数
MqttConnectOptions options = new MqttConnectOptions();
// 是否清除会话:
// true - 断开后清除订阅(推荐后端使用)
// false - 保留会话(多用于设备端)
options.setCleanSession(props.isCleanSession());
// 心跳时间(秒)
options.setKeepAliveInterval(props.getKeepAlive());
// 连接超时时间(秒)
options.setConnectionTimeout(props.getTimeout());
// 自动重连(非常重要,防止网络抖动)
options.setAutomaticReconnect(true);
// 设置用户名密码(如果配置了)
if (props.getUsername() != null && !props.getUsername().trim().isEmpty()) {
options.setUserName(props.getUsername().trim());
}
if (props.getPassword() != null && !props.getPassword().trim().isEmpty()) {
options.setPassword(props.getPassword().toCharArray());
}
// 3. 设置 MQTT 回调
mqttClient.setCallback(new MqttCallbackExtended() {
/**
* 连接成功(或重连成功)回调
*/
@Override
public void connectComplete(boolean reconnect, String serverURI) {
log.info("[MQTT] 连接成功 reconnect={}, serverURI={}", reconnect, serverURI);
// 连接成功后订阅 Topic
subscribe(props);
}
/**
* 连接丢失回调
*/
@Override
public void connectionLost(Throwable cause) {
log.warn("[MQTT] 连接断开", cause);
}
/**
* 接收到消息回调
*
* @param topic 消息主题(用于区分设备、类型)
* @param message 消息内容
*/
@Override
public void messageArrived(String topic, MqttMessage message) {
String payload = new String(message.getPayload(), StandardCharsets.UTF_8);
mqttMessageDispatcher.dispatch(topic, payload);
}
/**
* 消息发送完成回调(发布消息后触发)
*/
@Override
public void deliveryComplete(IMqttDeliveryToken token) {
// 后端下发指令时可用于确认发送完成
}
});
// 4. 连接 Broker
log.info("[MQTT] 正在连接 Broker{}", props.getBroker());
mqttClient.connect(options);
log.info("[MQTT] MQTT 已连接");
// 5. 启动时先订阅一次(防止 connectComplete 未触发)
subscribe(props);
return mqttClient;
}
/**
* 订阅 Topic支持通配符
*/
private void subscribe(MqttProperties props) {
if (mqttClient == null || !mqttClient.isConnected()) {
log.warn("[MQTT] 订阅失败:客户端未连接");
return;
}
if (props.getTopics() == null || props.getTopics().isEmpty()) {
log.warn("[MQTT] 未配置订阅 Topic");
return;
}
try {
for (String topic : props.getTopics()) {
if (topic == null || topic.trim().isEmpty()) {
continue;
}
mqttClient.subscribe(topic.trim(), props.getQos());
log.info("[MQTT] 已订阅 Topic{}QoS={}", topic.trim(), props.getQos());
}
} catch (Exception e) {
log.error("[MQTT] 订阅 Topic 失败", e);
}
}
/**
* Spring 容器关闭时,优雅断开 MQTT 连接
*/
@PreDestroy
public void destroy() {
try {
if (mqttClient != null) {
if (mqttClient.isConnected()) {
mqttClient.disconnect();
}
mqttClient.close();
}
} catch (Exception ignored) {
}
}
}

View File

@@ -1,46 +0,0 @@
package com.zg.project.wisdom.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* MQTT 配置属性(对应 application.yml 中 mqtt.*
*/
@Data
@Component
@ConfigurationProperties(prefix = "mqtt")
public class MqttProperties {
/** 是否启用 MQTT */
private boolean enabled;
/** Broker 地址tcp://192.168.1.29:1883 */
private String broker;
/** 客户端ID在 EMQX 中唯一) */
private String clientId;
/** 账号 */
private String username;
/** 密码 */
private String password;
/** 是否清除会话 */
private boolean cleanSession = true;
/** 心跳时间(秒) */
private int keepAlive = 30;
/** 连接超时时间(秒) */
private int timeout = 10;
/** 默认 QoS */
private int qos = 1;
/** 订阅 Topic 列表(支持通配符) */
private List<String> topics;
}

View File

@@ -1,74 +0,0 @@
package com.zg.project.wisdom.config;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets;
/**
* MQTT 消息发布客户端
*
* 作用说明:
* - 专门用于向指定设备下发控制指令
* - 例如:开启 / 关闭 / 重启 传感器
*
* 说明:
* - 依赖同一个 MqttClient
* - 发布消息时使用“精确 Topic”不能使用通配符
*/
@Slf4j
@Component
public class MqttPublishClient {
/**
* MQTT 客户端(由 Spring 注入)
*/
private final MqttClient mqttClient;
public MqttPublishClient(MqttClient mqttClient) {
this.mqttClient = mqttClient;
}
/**
* 发布 MQTT 消息(默认 QoS=1不保留消息
*
* @param topic 目标 Topic必须是具体设备的 Topic
* @param payload 消息内容JSON 字符串)
*/
public void publish(String topic, String payload) {
publish(topic, payload, 1, false);
}
/**
* 发布 MQTT 消息(自定义参数)
*/
public void publish(String topic, String payload, int qos, boolean retained) {
// 如果 MQTT 未启用或未初始化
if (mqttClient == null) {
throw new IllegalStateException("MQTT 客户端未初始化mqtt.enabled=false");
}
// 如果 MQTT 尚未连接
if (!mqttClient.isConnected()) {
throw new IllegalStateException("MQTT 客户端未连接到 Broker");
}
try {
MqttMessage msg = new MqttMessage(payload.getBytes(StandardCharsets.UTF_8));
msg.setQos(qos);
msg.setRetained(retained);
mqttClient.publish(topic, msg);
log.info("[MQTT] 指令已发送 topic={}, qos={}, retained={}, payload={}",
topic, qos, retained, payload);
} catch (Exception e) {
log.error("[MQTT] 指令发送失败 topic=" + topic, e);
throw new RuntimeException(e);
}
}
}

View File

@@ -10,6 +10,7 @@ import com.zg.common.utils.StringUtils;
import com.zg.project.wisdom.domain.dto.*;
import com.zg.project.wisdom.domain.vo.DeliveryBillVO;
import com.zg.project.wisdom.service.QwenOcrRemoteService;
import com.zg.project.wisdom.service.RkStatisticsService;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@@ -96,14 +97,28 @@ public class RkInfoController extends BaseController
}
// @PreAuthorize("@ss.hasPermi('wisdom:stock:list')")
@PostMapping("/bill/groups")
public TableDataInfo billGroups(@RequestBody RkInfoQueryDTO query) {
// 分页
PageHelper.startPage(query.getPageNum(), query.getPageSize());
// 查询
List<RkInfo> rows = rkInfoService.selectGroupedByBill(query);
// @PostMapping("/bill/groups")
// public TableDataInfo billGroups(@RequestBody RkInfoQueryDTO query) {
// // 分页
// PageHelper.startPage(query.getPageNum(), query.getPageSize());
// // 查询
// List<RkInfo> rows = rkInfoService.selectGroupedByBill(query);
//
// return getDataTable(rows);
// }
return getDataTable(rows);
/**
* 获取单据列表(轻量级:仅返回单据头公共信息,不聚合明细字段)
*/
@PostMapping("/bill/groups")
public TableDataInfo billHeaderList(@RequestBody RkInfoQueryDTO query) {
// 1. 分页
PageHelper.startPage(query.getPageNum(), query.getPageSize());
// 2. 查询
List<RkInfo> list = rkInfoService.selectBillHeaderList(query);
return getDataTable(list);
}
/**
@@ -365,16 +380,5 @@ public class RkInfoController extends BaseController
}
@PostMapping("/statistics")
public Map<String, Object> statistics(@RequestBody RkInfoQueryDTO dto) {
Long sumMoney = rkInfoService.selectStatistics(dto);
Long pcdeCount = rkInfoService.selectPcde(dto);
Map<String, Object> dataInfo = new HashMap<>();
dataInfo.put("sumMoney", sumMoney);
dataInfo.put("pcdeCount", pcdeCount);
return dataInfo;
}
}

View File

@@ -1,6 +1,7 @@
package com.zg.project.wisdom.controller;
import com.zg.framework.web.domain.AjaxResult;
import com.zg.project.wisdom.domain.dto.RkInfoQueryDTO;
import com.zg.project.wisdom.domain.vo.*;
import com.zg.project.wisdom.service.RkStatisticsService;
import com.zg.project.wisdom.service.WarehouseStatService;
@@ -178,4 +179,10 @@ public class RkStatisticsController {
List<IOBucketVO> rows = rkStatisticsService.getIOBuckets(range);
return AjaxResult.success(rows);
}
@PostMapping("/statistics")
public Map<String, Object> statistics(@RequestBody RkInfoQueryDTO dto) {
// 调用新的合并查询方法
return rkStatisticsService.selectStockStatistics(dto);
}
}

View File

@@ -20,10 +20,10 @@ public class RkInfoQueryDTO extends RkInfo {
private Integer pageSize;
/** 开始时间 */
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date statDate;
/** 结束时间 */
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date endDate;
}

View File

@@ -1,6 +1,7 @@
package com.zg.project.wisdom.mapper;
import java.util.List;
import java.util.Map;
import com.zg.project.inventory.domain.vo.PcdeCntVO;
import com.zg.project.inventory.domain.vo.RkInfoMatchVO;
@@ -39,6 +40,8 @@ public interface RkInfoMapper
@Param("needAudit") Integer needAudit);
List<RkInfo> selectBillHeaderList(@Param("q") RkInfoQueryDTO query,
@Param("needAudit") Integer needAudit);
/**
* 修改库存单据主
@@ -234,10 +237,6 @@ public interface RkInfoMapper
*/
List<RkInfo> selectAllRkInfo(RkInfo query);
Long selectStatistics(RkInfo query);
Long selectPcde(RkInfo query);
int updateBillInfo(RkInfo query);
@@ -263,4 +262,7 @@ public interface RkInfoMapper
RkInfo selectHeaderByBillNo(String billNo);
String selectMaxBillNo(@Param("prefix") String prefix);
Map<String, Object> selectStockStatistics(RkInfoQueryDTO query);
}

View File

@@ -1,55 +0,0 @@
package com.zg.project.wisdom.mqtt.dispatcher;
import com.zg.project.wisdom.mqtt.handler.HumiSensorHandler;
import com.zg.project.wisdom.mqtt.handler.SmokeSensorHandler;
import com.zg.project.wisdom.mqtt.handler.TempSensorHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* MQTT消息分发器
* 负责根据MQTT主题(topic)将消息分发给相应的传感器处理器
*/
@Slf4j
@Component
public class MqttMessageDispatcher {
@Autowired
private TempSensorHandler tempSensorHandler;
@Autowired
private HumiSensorHandler humiSensorHandler;
@Autowired
private SmokeSensorHandler smokeSensorHandler;
/**
* 分发MQTT消息到对应的处理器
* 根据topic的前缀判断消息类型并调用相应的处理器进行处理
*
* @param topic MQTT主题用于判断消息类型
* @param payload 消息负载,包含具体的传感器数据
*/
public void dispatch(String topic, String payload) {
// 处理温度传感器消息
if (topic.startsWith("zg/wms/test/temp/")) {
tempSensorHandler.handle(topic, payload);
return;
}
// 处理湿度传感器消息
if (topic.startsWith("zg/wms/test/humi/")) {
humiSensorHandler.handle(topic, payload);
return;
}
// 处理烟雾传感器消息
if (topic.startsWith("zg/wms/test/smoke/")) {
smokeSensorHandler.handle(topic, payload);
return;
}
log.warn("未知 MQTT Topic: {}", topic);
}
}

View File

@@ -1,43 +0,0 @@
package com.zg.project.wisdom.mqtt.handler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 湿度传感器 Handler
* Topic 示例zg/wms/test/humi/{deviceId}
*/
@Slf4j
@Component
public class HumiSensorHandler {
public static final String TOPIC_PREFIX = "zg/wms/test/humi/";
/**
* 处理湿度传感器消息
* @param topic MQTT主题
* @param payload 消息负载
*/
public void handle(String topic, String payload) {
String deviceId = resolveDeviceId(topic, TOPIC_PREFIX);
log.info("[HUMI] deviceId={}, topic={}, payload={}", deviceId, topic, payload);
// TODO: 这里后续写湿度业务逻辑
}
/**
* 从MQTT主题中解析设备ID
* @param topic MQTT主题
* @param prefix 主题前缀
* @return 设备ID
*/
private String resolveDeviceId(String topic, String prefix) {
if (topic == null || !topic.startsWith(prefix)) {
return "";
}
// 提取设备ID部分
return topic.substring(prefix.length());
}
}

View File

@@ -1,47 +0,0 @@
package com.zg.project.wisdom.mqtt.handler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 烟雾传感器 Handler
* Topic 示例zg/wms/test/smoke/{deviceId}
*/
@Slf4j
@Component
public class SmokeSensorHandler {
/**
* MQTT主题前缀用于烟雾传感器消息
*/
public static final String TOPIC_PREFIX = "zg/wms/test/smoke/";
/**
* 处理烟雾传感器消息
*
* @param topic MQTT主题
* @param payload 消息负载内容
*/
public void handle(String topic, String payload) {
String deviceId = resolveDeviceId(topic, TOPIC_PREFIX);
log.info("[SMOKE] deviceId={}, topic={}, payload={}", deviceId, topic, payload);
// TODO: 这里后续写烟雾业务逻辑
}
/**
* 从MQTT主题中解析设备ID
*
* @param topic 完整的MQTT主题
* @param prefix 主题前缀
* @return 设备ID如果主题格式不正确则返回空字符串
*/
private String resolveDeviceId(String topic, String prefix) {
if (topic == null || !topic.startsWith(prefix)) {
return "";
}
// 提取前缀后的设备ID部分
return topic.substring(prefix.length());
}
}

View File

@@ -1,46 +0,0 @@
package com.zg.project.wisdom.mqtt.handler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 温度传感器 Handler
* Topic 示例zg/wms/test/temp/{deviceId}
*/
@Slf4j
@Component
public class TempSensorHandler {
/**
* MQTT主题前缀用于温度传感器数据
*/
public static final String TOPIC_PREFIX = "zg/wms/test/temp/";
/**
* 处理温度传感器数据
*
* @param topic MQTT主题格式为 zg/wms/test/temp/{deviceId}
* @param payload 传感器数据载荷
*/
public void handle(String topic, String payload) {
String deviceId = resolveDeviceId(topic, TOPIC_PREFIX);
log.info("[TEMP] deviceId={}, topic={}, payload={}", deviceId, topic, payload);
// TODO: 这里后续写温度业务逻辑解析JSON、落库、告警等
}
/**
* 从MQTT主题中解析设备ID
*
* @param topic MQTT主题
* @param prefix 主题前缀
* @return 设备ID如果主题格式不正确则返回空字符串
*/
private String resolveDeviceId(String topic, String prefix) {
if (topic == null || !topic.startsWith(prefix)) {
return "";
}
return topic.substring(prefix.length());
}
}

View File

@@ -1,6 +1,7 @@
package com.zg.project.wisdom.service;
import java.util.List;
import java.util.Map;
import com.zg.project.inventory.domain.dto.QueryDTO;
import com.zg.project.inventory.domain.vo.ChartDataVO;
@@ -38,6 +39,10 @@ public interface IRkInfoService
*/
List<RkInfo> selectGroupedByBill(RkInfoQueryDTO query);
// 接口
List<RkInfo> selectBillHeaderList(RkInfoQueryDTO query);
/**
* 修改库存单据主
*
@@ -168,11 +173,6 @@ public interface IRkInfoService
*/
List<RkInfo> selectAllRkInfo(RkInfo query);
Long selectStatistics(RkInfo query);
Long selectPcde(RkInfo query);
public int updateBillInfo(RkInfo rkInfo);
/**

View File

@@ -1,5 +1,6 @@
package com.zg.project.wisdom.service;
import com.zg.project.wisdom.domain.dto.RkInfoQueryDTO;
import com.zg.project.wisdom.domain.vo.*;
import javax.servlet.http.HttpServletResponse;
@@ -87,4 +88,11 @@ public interface RkStatisticsService {
* 规则:排除 rk_info 中未出库is_chuku=0 或 NULL的占用库位
*/
List<SceneAvailableVO> listAvailableByWarehouse(String warehouseCode);
/**
* 库存统计
* @param dto 查询参数
* @return { "total": 0, "rows": List<StockStatVO> }
*/
Map<String, Object> selectStockStatistics(RkInfoQueryDTO dto);
}

View File

@@ -183,7 +183,13 @@ public class RkInfoServiceImpl implements IRkInfoService
boolean needAudit = "1".equals(configService.selectConfigByKey("rk.audit.enabled"));
// 直接传给 Mapper不改 DTO 结构,走多参数方式
return rkInfoMapper.selectGroupedByBill(query, needAudit ? 1 : 0);
// return rkInfoMapper.selectGroupedByBillSimple(query);
}
@Override
public List<RkInfo> selectBillHeaderList(RkInfoQueryDTO query) {
// 同样传入审核开关逻辑(保留原逻辑一致性)
boolean needAudit = "1".equals(configService.selectConfigByKey("rk.audit.enabled"));
return rkInfoMapper.selectBillHeaderList(query, needAudit ? 1 : 0);
}
@Override
@@ -1388,16 +1394,6 @@ public class RkInfoServiceImpl implements IRkInfoService
return list;
}
@Override
public Long selectStatistics(RkInfo query) {
return rkInfoMapper.selectStatistics(query);
}
@Override
public Long selectPcde(RkInfo query) {
return rkInfoMapper.selectPcde(query);
}
@Override
public int updateBillInfo(RkInfo query) {
return rkInfoMapper.updateBillInfo(query);

View File

@@ -5,10 +5,13 @@ import com.zg.common.exception.ServiceException;
import com.zg.common.utils.DateUtils;
import com.zg.common.utils.StringUtils;
import com.zg.common.utils.poi.ExcelUtil;
import com.zg.project.wisdom.domain.dto.RkInfoQueryDTO;
import com.zg.project.wisdom.domain.vo.*;
import com.zg.project.wisdom.mapper.GysJhMapper;
import com.zg.project.wisdom.mapper.RkInfoMapper;
import com.zg.project.wisdom.mapper.RkStatisticsMapper;
import com.zg.project.wisdom.service.RkStatisticsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@@ -23,9 +26,12 @@ import java.util.*;
@Service
public class RkStatisticsServiceImpl implements RkStatisticsService {
@Resource
@Autowired
private RkStatisticsMapper rkStatisticsMapper;
@Autowired
private RkInfoMapper rkInfoMapper;
@Override
public List<Map<String, Object>> getAgeStatsAsList() {
@@ -426,4 +432,24 @@ public class RkStatisticsServiceImpl implements RkStatisticsService {
}
return list;
}
@Override
public Map<String, Object> selectStockStatistics(RkInfoQueryDTO query) {
if (query.getIsChuku() == null) {
query.setIsChuku("0"); // 默认只查在库
}
Map<String, Object> result = rkInfoMapper.selectStockStatistics(query);
// 处理空值情况,防止前端拿到 null 报错
if (result == null) {
result = new HashMap<>();
}
result.putIfAbsent("sumMoney", 0);
result.putIfAbsent("sumQty", 0);
result.putIfAbsent("pcdeCount", 0);
return result;
}
}

View File

@@ -7,9 +7,9 @@ spring:
# 主库数据源
master:
# url: jdbc:mysql://101.132.133.142:3306/wisdom?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
url: jdbc:mysql://192.168.1.28:3306/wisdom?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
# url: jdbc:mysql://192.168.1.28:3306/wisdom?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
# url: jdbc:mysql://192.168.1.192:3306/wisdom?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
# url: jdbc:mysql://192.168.1.251:3306/wisdom?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
url: jdbc:mysql://192.168.1.251:3306/wisdom?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
# url: jdbc:mysql://localhost:3306/wisdom?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: shzg

View File

@@ -170,51 +170,3 @@ mock:
#配送系统中调用大模型进行图片识别
qwen-ocr:
base-url: http://192.168.1.253:8087/ocr/extractErpByBase64
# =========================
# MQTT 配置EMQX 接入)
# =========================
mqtt:
# 是否启用 MQTT 功能
# true系统启动时自动连接 EMQX 并订阅 Topic
# false不初始化 MQTT 客户端
enabled: true
# MQTT Broker 地址
# tcp://IP:1883 —— MQTT TCP
# ws://IP:8083 —— MQTT WebSocket
# ssl://IP:8883 —— MQTT SSL
broker: tcp://192.168.1.29:1883
# MQTT 客户端 ID在 EMQX 中唯一)
# 建议:系统名 + 模块名,避免重复
clientId: zg-wms-backend-test
# MQTT 账号EMQX Dashboard 中配置)
username: demo02
# MQTT 密码
password: admin123
# 是否清除会话
# true :断开后清除订阅和未接收消息(推荐后端使用)
# false :保留会话(适合设备端)
cleanSession: true
# 心跳时间(秒)
# 客户端与 Broker 保持连接的心跳间隔
keepAlive: 30
# 连接超时时间(秒)
timeout: 10
# 默认消息 QoS
# 0最多一次不保证到达性能最好
# 1至少一次推荐业务数据常用
# 2仅一次最严格性能最差
qos: 1
# 订阅的 Topic 列表(支持通配符)
# 建议先使用测试 Topic确认链路正常后再接入真实业务 Topic
topics:
- zg/wms/test/#

View File

@@ -587,6 +587,160 @@
</select>
<select id="selectBillHeaderList" resultMap="RkInfoResult">
SELECT
-- 3. 外层:关联字典表获取名称
a.show_bill_no AS bill_no, -- 最终展示的单据号
a.bill_no AS bill_no, -- 原始入库单号(兼容实体)
a.bill_no_ck AS bill_no_ck, -- 原始出库单号
-- 状态与标识
a.is_chuku,
a.is_delivery,
a.id, -- 取聚合后的最小ID作为主键标识
-- 入库维度的公共字段
a.rk_type,
si.type_name AS rk_type_name,
a.wl_type,
mt.type_name AS wl_type_name,
a.rk_time,
a.lihuo_y,
ru.user_name AS lihuo_y_name,
-- 仓库信息
a.cangku,
wh.warehouse_name AS cangku_name,
wh.parent_warehouse_code AS parent_warehouse_code,
wh.parent_warehouse_name AS parent_warehouse_name,
wh.warehouse_code AS warehouse_code,
wh.warehouse_name AS warehouse_name,
-- 出库维度的公共字段
a.ck_type,
so.type_name AS ck_type_name,
a.ly_time,
a.ck_lihuo_y,
u.user_name AS ck_lihuo_y_name, -- 注意:这里对应 XML 里的 u 表别名
a.team_code,
ct.team_name AS team_name,
-- 创建时间
a.create_time
FROM (
SELECT
-- 2. 中间层:按单据号聚合
t.show_bill_no,
MIN(t.id) AS id,
-- 单据头信息聚合(取最大值或最小值均可,因为同一单据号下这些字段是一致的)
MAX(t.bill_no) AS bill_no,
MAX(t.bill_no_ck) AS bill_no_ck,
MAX(t.is_chuku) AS is_chuku,
MAX(t.is_delivery) AS is_delivery,
MIN(t.rk_type) AS rk_type,
MIN(t.wl_type) AS wl_type,
MIN(t.rk_time) AS rk_time,
MIN(t.lihuo_y) AS lihuo_y,
MIN(t.cangku) AS cangku,
MIN(t.ck_type) AS ck_type,
MIN(t.ly_time) AS ly_time,
MIN(t.ck_lihuo_y) AS ck_lihuo_y,
MIN(t.team_code) AS team_code,
MAX(t.create_time) AS create_time
FROM (
-- 1. 内层:过滤并计算 show_bill_no
SELECT
ri.id,
ri.bill_no,
ri.bill_no_ck,
ri.rk_type,
ri.wl_type,
ri.cangku,
ri.rk_time,
ri.lihuo_y,
ri.is_chuku,
ri.is_delivery,
ri.ck_type,
ri.ly_time,
ri.ck_lihuo_y,
ri.team_code,
ri.create_time,
-- 计算逻辑:如果是出库状态且有出库单号,则以出库单号为主
CASE
WHEN ri.is_chuku IN ('1','3') AND ri.bill_no_ck IS NOT NULL AND ri.bill_no_ck != ''
THEN ri.bill_no_ck
ELSE ri.bill_no
END AS show_bill_no
FROM rk_info ri
<where>
AND ri.is_delete = 0
<!-- 关键字搜索:依然保留对项目号、物料号等的支持,方便用户查找 -->
<if test="q.keyword != null and q.keyword != ''">
AND (
ri.xm_no LIKE concat('%', #{q.keyword}, '%')
OR ri.wl_no LIKE concat('%', #{q.keyword}, '%')
OR ri.gys_mc LIKE concat('%', #{q.keyword}, '%')
)
</if>
<if test="q.isChukuList != null and q.isChukuList.size() > 0">
AND ri.is_chuku IN
<foreach collection="q.isChukuList" item="s" open="(" separator="," close=")">
#{s}
</foreach>
</if>
<if test="(q.isChukuList == null or q.isChukuList.size() == 0) and q.isChuku != null and q.isChuku != ''">
AND ri.is_chuku = #{q.isChuku}
</if>
<if test="q.ckType != null and q.ckType != ''">
AND ri.ck_type = #{q.ckType}
</if>
<if test="q.cangku != null and q.cangku != ''">
AND ri.cangku = #{q.cangku}
</if>
<if test="q.billNoCk != null and q.billNoCk != ''">
AND ri.bill_no_ck = #{q.billNoCk}
</if>
<if test="q.billNo != null and q.billNo != ''">
AND ri.bill_no = #{q.billNo}
</if>
<if test="q.startTime != null">
AND ri.rk_time &gt;= #{q.startTime}
</if>
<if test="q.endTime != null">
AND ri.rk_time &lt;= #{q.endTime}
</if>
<if test="q.statDate != null">
AND ri.ly_time &gt;= #{q.statDate}
</if>
<if test="q.endDate != null">
AND ri.ly_time &lt;= #{q.endDate}
</if>
</where>
) t
GROUP BY t.show_bill_no
) a
-- 字典关联
LEFT JOIN stock_in_type si ON a.rk_type = si.type_code
LEFT JOIN stock_out_type so ON a.ck_type = so.type_code
LEFT JOIN warehouse_info wh ON a.cangku = wh.warehouse_code
LEFT JOIN sys_user u ON a.ck_lihuo_y = u.user_id -- 出库理货员
LEFT JOIN sys_user ru ON a.lihuo_y = ru.user_id -- 入库理货员
LEFT JOIN construction_team ct ON a.team_code = ct.team_code
LEFT JOIN material_type mt ON a.wl_type = mt.type_code
-- 排序优化:优先按出库时间排序;若无出库时间(入库单),则按入库时间排序
ORDER BY COALESCE(a.ly_time, a.rk_time) DESC, a.id DESC
</select>
<!-- ================== /按单据分组查询 ================== -->
<select id="selectRkInfoById" parameterType="Long" resultMap="RkInfoResult">
@@ -1120,148 +1274,6 @@
ORDER BY ri.create_time DESC, ri.id DESC
</select>
<select id="selectStatistics" resultType="java.lang.Long" parameterType="java.lang.Object" >
SELECT sum(ri.ht_dj * ri.real_qty)
FROM rk_info ri
<where>
(ri.is_delete = '0' OR ri.is_delete = 0 OR ri.is_delete IS NULL)
<if test="isChuku != null and isChuku != ''">
AND ri.is_chuku = #{isChuku}
</if>
<if test="cangku != null and cangku != ''">
AND ri.cangku = #{cangku}
</if>
<if test="rkType != null and rkType != ''">
AND ri.rk_type = #{rkType}
</if>
<if test="startTime != null">
AND ri.rk_time <![CDATA[ >= ]]> #{startTime}
</if>
<if test="endTime != null">
AND ri.rk_time <![CDATA[ <= ]]> #{endTime}
</if>
<if test="lyStartTime != null and lyStartTime != ''">
AND ri.ly_time <![CDATA[ >= ]]> #{lyStartTime}
</if>
<if test="lyEndTime != null and lyEndTime != ''">
AND ri.ly_time <![CDATA[ <= ]]> #{lyEndTime}
</if>
<if test="xmNo != null and xmNo != ''">
AND ri.xm_no LIKE CONCAT('%', #{xmNo}, '%')
</if>
<if test="xmMs != null and xmMs != ''">
AND ri.xm_ms LIKE CONCAT('%', #{xmMs}, '%')
</if>
<if test="wlNo != null and wlNo != ''">
AND ri.wl_no LIKE CONCAT('%', #{wlNo}, '%')
</if>
<if test="wlMs != null and wlMs != ''">
AND ri.wl_ms LIKE CONCAT('%', #{wlMs}, '%')
</if>
<if test="gysNo != null and gysNo != ''">
AND ri.gys_no LIKE CONCAT('%', #{gysNo}, '%')
</if>
<if test="gysMc != null and gysMc != ''">
AND ri.gys_mc LIKE CONCAT('%', #{gysMc}, '%')
</if>
<if test="sapNo != null and sapNo != ''">
AND ri.sap_no LIKE CONCAT('%', #{sapNo}, '%')
</if>
<if test="billNo != null and billNo != ''">
AND ri.bill_no LIKE CONCAT('%', #{billNo}, '%')
</if>
<if test="billNoCk != null and billNoCk != ''">
AND ri.bill_no_ck LIKE CONCAT('%', #{billNoCk}, '%')
</if>
<if test="ckType != null and ckType != ''">
AND ri.ck_type LIKE CONCAT('%', #{ckType}, '%')
</if>
<if test="pcode != null and pcode != ''">
AND ri.pcode LIKE CONCAT('%', #{pcode}, '%')
</if>
<if test="fycde1 != null and fycde1 != ''">
AND ri.fycde_1 LIKE CONCAT('%', #{fycde1}, '%')
</if>
<if test="fycde2 != null and fycde2 != ''">
AND ri.fycde_2 LIKE CONCAT('%', #{fycde2}, '%')
</if>
</where>
</select>
<select id="selectPcde" resultType="java.lang.Long" parameterType="java.lang.Object" >
SELECT count(distinct pcode)
FROM rk_info ri
<where>
(ri.is_delete = '0' OR ri.is_delete = 0 OR ri.is_delete IS NULL)
<if test="isChuku != null and isChuku != ''">
AND ri.is_chuku = #{isChuku}
</if>
<if test="cangku != null and cangku != ''">
AND ri.cangku = #{cangku}
</if>
<if test="startTime != null">
AND ri.rk_time <![CDATA[ >= ]]> #{startTime}
</if>
<if test="endTime != null">
AND ri.rk_time <![CDATA[ <= ]]> #{endTime}
</if>
<if test="lyStartTime != null and lyStartTime != ''">
AND ri.ly_time <![CDATA[ >= ]]> #{lyStartTime}
</if>
<if test="lyEndTime != null and lyEndTime != ''">
AND ri.ly_time <![CDATA[ <= ]]> #{lyEndTime}
</if>
<if test="xmNo != null and xmNo != ''">
AND ri.xm_no LIKE CONCAT('%', #{xmNo}, '%')
</if>
<if test="xmMs != null and xmMs != ''">
AND ri.xm_ms LIKE CONCAT('%', #{xmMs}, '%')
</if>
<if test="wlNo != null and wlNo != ''">
AND ri.wl_no LIKE CONCAT('%', #{wlNo}, '%')
</if>
<if test="wlMs != null and wlMs != ''">
AND ri.wl_ms LIKE CONCAT('%', #{wlMs}, '%')
</if>
<if test="gysNo != null and gysNo != ''">
AND ri.gys_no LIKE CONCAT('%', #{gysNo}, '%')
</if>
<if test="gysMc != null and gysMc != ''">
AND ri.gys_mc LIKE CONCAT('%', #{gysMc}, '%')
</if>
<if test="sapNo != null and sapNo != ''">
AND ri.sap_no LIKE CONCAT('%', #{sapNo}, '%')
</if>
<if test="billNo != null and billNo != ''">
AND ri.bill_no LIKE CONCAT('%', #{billNo}, '%')
</if>
<if test="billNoCk != null and billNoCk != ''">
AND ri.bill_no_ck LIKE CONCAT('%', #{billNoCk}, '%')
</if>
<if test="ckType != null and ckType != ''">
AND ri.ck_type LIKE CONCAT('%', #{ckType}, '%')
</if>
<if test="pcode != null and pcode != ''">
AND ri.pcode LIKE CONCAT('%', #{pcode}, '%')
</if>
<if test="fycde1 != null and fycde1 != ''">
AND ri.fycde_1 LIKE CONCAT('%', #{fycde1}, '%')
</if>
<if test="fycde2 != null and fycde2 != ''">
AND ri.fycde_2 LIKE CONCAT('%', #{fycde2}, '%')
</if>
</where>
</select>
<select id="selectDeliveryCkList"
parameterType="com.zg.project.wisdom.domain.RkInfo"
resultMap="RkInfoResult">
@@ -1326,6 +1338,95 @@
AND bill_no LIKE CONCAT(#{prefix}, '%')
</select>
<!-- 聚合统计:总金额、总数量、库位数 -->
<select id="selectStockStatistics" resultType="java.util.Map">
SELECT
-- 1. 总金额:合同单价 * 实际入库数量 (处理 null 为 0)
COALESCE(SUM(ri.ht_dj * ri.real_qty), 0) AS sumMoney,
-- 2. 总数量:实际入库数量求和
COALESCE(SUM(ri.real_qty), 0) AS sumQty,
-- 3. 库位数:去重统计 pcode
COUNT(DISTINCT ri.pcode) AS pcdeCount
FROM rk_info ri
<where>
-- 基础过滤:未删除
(ri.is_delete = '0' OR ri.is_delete = 0 OR ri.is_delete IS NULL)
-- 核心过滤:在库状态 (由Service传参或此处硬编码 '0')
<if test="isChuku != null and isChuku != ''">
AND ri.is_chuku = #{isChuku}
</if>
<!-- 以下为动态查询条件,直接复用你 selectRkInfoList 的逻辑 -->
<if test="cangku != null and cangku != ''">
AND ri.cangku = #{cangku}
</if>
<if test="rkType != null and rkType != ''">
AND ri.rk_type = #{rkType}
</if>
<if test="wlType != null and wlType != ''">
AND ri.wl_type = #{wlType}
</if>
<!-- 时间范围 -->
<if test="startTime != null">
AND ri.rk_time &gt;= #{startTime}
</if>
<if test="endTime != null">
AND ri.rk_time &lt;= #{endTime}
</if>
<!-- 模糊搜索 (复用原来的逻辑,确保结果与列表一致) -->
<if test="keyword != null and keyword != ''">
AND (
ri.xm_no LIKE concat('%', #{keyword}, '%')
OR ri.xm_ms LIKE concat('%', #{keyword}, '%')
OR ri.wl_no LIKE concat('%', #{keyword}, '%')
OR ri.wl_ms LIKE concat('%', #{keyword}, '%')
OR ri.gys_no LIKE concat('%', #{keyword}, '%')
OR ri.gys_mc LIKE concat('%', #{keyword}, '%')
OR ri.sap_no LIKE concat('%', #{keyword}, '%')
OR ri.bill_no LIKE concat('%', #{keyword}, '%')
OR ri.bill_no_ck LIKE concat('%', #{keyword}, '%')
OR ri.ck_type LIKE concat('%', #{keyword}, '%')
OR ri.pcode LIKE concat('%', #{keyword}, '%')
)
</if>
<!-- 单字段精确/模糊匹配 -->
<if test="xmNo != null and xmNo != ''">
AND ri.xm_no LIKE CONCAT('%', #{xmNo}, '%')
</if>
<if test="xmMs != null and xmMs != ''">
AND ri.xm_ms LIKE CONCAT('%', #{xmMs}, '%')
</if>
<if test="wlNo != null and wlNo != ''">
AND ri.wl_no LIKE CONCAT('%', #{wlNo}, '%')
</if>
<if test="wlMs != null and wlMs != ''">
AND ri.wl_ms LIKE CONCAT('%', #{wlMs}, '%')
</if>
<if test="gysNo != null and gysNo != ''">
AND ri.gys_no LIKE CONCAT('%', #{gysNo}, '%')
</if>
<if test="gysMc != null and gysMc != ''">
AND ri.gys_mc LIKE CONCAT('%', #{gysMc}, '%')
</if>
<if test="sapNo != null and sapNo != ''">
AND ri.sap_no LIKE CONCAT('%', #{sapNo}, '%')
</if>
<if test="billNo != null and billNo != ''">
AND ri.bill_no LIKE CONCAT('%', #{billNo}, '%')
</if>
<if test="pcode != null and pcode != ''">
AND ri.pcode LIKE CONCAT('%', #{pcode}, '%')
</if>
</where>
</select>
<update id="updateBillInfo" parameterType="com.zg.project.wisdom.domain.RkInfo">
UPDATE rk_info
<set>