This commit is contained in:
tangzh 2025-05-30 11:36:08 +08:00
parent 51a74d15be
commit 439526cba2
26 changed files with 872 additions and 153 deletions

View File

@ -0,0 +1,25 @@
/*
* Copyright 2019-2025 Tz
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.zhengjie.modules.security.config;
public class WebSocketConfig {
public static final String PING = "ping";
public static final String PONG = "pong";
public static final String TOPIC_PREFIX = "sdcp";
}

View File

@ -0,0 +1,10 @@
package me.zhengjie.modules.security.config.enums;
public enum CapabilitieEnum {
FILE_TRANSFER, // 支持文件传输
PRINT_CONTROL, // 支持打印控制
VIDEO_STREAM; // 支持视频流传输
}

View File

@ -0,0 +1,62 @@
/*
* Copyright 2019-2025 Tz
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.zhengjie.modules.security.config.enums;
/**
* PreviousStatus与CurrentStatus定义
*/
public enum CurrentStatusEnum {
SDCP_MACHINE_STATUS_DEFAULT(-1, "默认/断开"),
SDCP_MACHINE_STATUS_IDLE(0, "空闲"),
SDCP_MACHINE_STATUS_PRINTING(1, "执行打印任务中"),
SDCP_MACHINE_STATUS_PREPARE(2, "打印准备过程中"), //
SDCP_MACHINE_STATUS_SPADE(3, "铲件过程中"),
;
private Integer code;
private String text;
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
CurrentStatusEnum(Integer code, String text) {
this.code = code;
this.text = text;
}
public static CurrentStatusEnum getEnumByNum(int value) {
for (CurrentStatusEnum os : CurrentStatusEnum.values()) {
if (value == os.getCode()) {
return os;
}
}
return null;
}
}

View File

@ -0,0 +1,66 @@
/*
* Copyright 2019-2025 Tz
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.zhengjie.modules.security.config.enums;
/**
* ErrorStatusEnum
*/
public enum ErrorStatusEnum {
SDCP_PRINT_ERROR_DEFAULT(-1, "默认"),
SDCP_PRINT_ERROR_NONE(0 ,"正常"),
SDCP_PRINT_ERROR_CHECKFILE(1 ,"文件校验失败"),
SDCP_PRINT_ERROR_DEVICE(2 ,"设备或加密出错"),
SDCP_PRINT_ERROR_PREPARE(3 ,"打印准备过程出错"),
SDCP_PRINT_ERROR_PRINT(4 ,"打印出错"),
SDCP_PRINT_ERROR_LIQUID(5 ,"加液失败"),
SDCP_PRINT_ERROR_SPADE(6 ,"铲件失败"),
SDCP_PRINT_ERROR_PIC(7 ,"图片异物"),
;
private Integer code;
private String text;
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
ErrorStatusEnum(Integer code, String text) {
this.code = code;
this.text = text;
}
public static ErrorStatusEnum getEnumByNum(int value) {
for (ErrorStatusEnum os : ErrorStatusEnum.values()) {
if (value == os.getCode()) {
return os;
}
}
return null;
}
}

View File

@ -0,0 +1,13 @@
package me.zhengjie.modules.security.config.enums;
public enum MethodEnum {
request,
response,
status,
attributes,
error,
notice;
}

View File

@ -15,15 +15,13 @@
*/ */
package me.zhengjie.modules.security.config.enums; package me.zhengjie.modules.security.config.enums;
/** public enum MsgEnum {
* @author ZhangHouYing
* @date 2019-08-10 9:56
*/
public enum MsgType {
/** 连接 */ /** 连接 */
CONNECT, CONNECT,
/** 关闭 */ /** 关闭 */
CLOSE, CLOSE,
/** 断开连接 */
DISCONNECT,
/** 信息 */ /** 信息 */
INFO, INFO,
/** 错误 */ /** 错误 */

View File

@ -0,0 +1,57 @@
package me.zhengjie.modules.security.config.enums;
/**
* PrintInfo 状态
*/
public enum PrintInfoStatusEnum {
SDCP_PRINT_STATUS_DEFAULT(-1, "默认"),
SDCP_PRINT_STATUS_IDLE(0, "空闲"),
SDCP_PRINT_STATUS_HOMING(1,"归零中"),
SDCP_PRINT_STATUS_DROPPING(2,"下降中"),
SDCP_PRINT_STATUS_EXPOSURING(3,"曝光中"),
SDCP_PRINT_STATUS_LIFTING(4,"抬升中"),
SDCP_PRINT_STATUS_PAUSING(5,"正在执行暂停动作中"),
SDCP_PRINT_STATUS_PAUSED(6,"已暂停"),
SDCP_PRINT_STATUS_STOPPING(7,"正在执行停止动作中"),
SDCP_PRINT_STATUS_STOPED(8,"已停止"),
SDCP_PRINT_STATUS_COMPLETE(9,"打印完成"),
SDCP_PRINT_STATUS_FILE_CHECKING(10, "文件检测中"),
SDCP_PRINT_STATUS_LIQUID(11, "加液中"),
SDCP_PRINT_STATUS_SPADE(12, "铲件中"),
;
private Integer code;
private String text;
PrintInfoStatusEnum(Integer code, String text) {
this.code = code;
this.text = text;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public static PrintInfoStatusEnum getEnumByNum(int value) {
for (PrintInfoStatusEnum os : PrintInfoStatusEnum.values()) {
if (value == os.getCode()) {
return os;
}
}
return null;
}
}

View File

@ -24,6 +24,7 @@ import java.sql.Timestamp;
import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import java.io.Serializable; import java.io.Serializable;
import java.util.List;
import java.util.Set; import java.util.Set;
import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.IdType;
@ -44,6 +45,9 @@ public class BusDevice implements Serializable {
@ApiModelProperty(value = "设备唯一标识") @ApiModelProperty(value = "设备唯一标识")
private Long id; private Long id;
@ApiModelProperty(value = "机器序号")
private String deviceCode;
@ApiModelProperty(value = "设备序列号(唯一)") @ApiModelProperty(value = "设备序列号(唯一)")
private String deviceSn; private String deviceSn;
@ -53,28 +57,31 @@ public class BusDevice implements Serializable {
@ApiModelProperty(value = "品牌") @ApiModelProperty(value = "品牌")
private String brand; private String brand;
@ApiModelProperty(value = "设备名称")
private String name;
@ApiModelProperty(value = "固件版本") @ApiModelProperty(value = "固件版本")
private String firmwareVersion; private String firmwareVersion;
@ApiModelProperty(value = "协议版本")
private String protocolVersion;
@ApiModelProperty(value = "放置位置") @ApiModelProperty(value = "放置位置")
private String location; private String location;
@ApiModelProperty(value = "设备状态(1=在线2=离线3=故障)") @ApiModelProperty(value = "设备状态(-1=默认/断开0=空闲1=任务中2=准备中3=铲件中)")
private Integer status; private Integer status;
@ApiModelProperty(value = "最近上线时间") @ApiModelProperty(value = "打印状态(-1默认0空闲1归零中2下降中3曝光中4抬升中5正在执行暂停动作中6已暂停7正在执行停止动作中8已停止9打印完成10文件检测中11加液中12铲件中)")
private Timestamp onlineTime; private Integer printStatus;
@ApiModelProperty(value = "最近下线时间") @ApiModelProperty(value = "异常状态(-1默认0正常1文件校验失败2设备或加密错误3打印准备过程出错4打印出错5加液失败6铲件失败7图片异物)")
private Timestamp offlineTime; private Integer errorStatus;
@ApiModelProperty(value = "累计时长(分钟)")
private Integer totalPrintTime;
@ApiModelProperty(value = "备注") @ApiModelProperty(value = "备注")
private String remark; private String remark;
@ApiModelProperty(value = "类型1打印机2摄像头") @ApiModelProperty(value = "类型1打印机")
private Integer type; private Integer type;
@ApiModelProperty(value = "创建者") @ApiModelProperty(value = "创建者")
@ -89,6 +96,9 @@ public class BusDevice implements Serializable {
@ApiModelProperty(value = "更新时间") @ApiModelProperty(value = "更新时间")
private Timestamp updateTime; private Timestamp updateTime;
@TableField(exist = false)
private List<BusDeviceCommand> commandList;
public void copy(BusDevice source){ public void copy(BusDevice source){
BeanUtil.copyProperties(source,this, CopyOptions.create().setIgnoreNullValue(true)); BeanUtil.copyProperties(source,this, CopyOptions.create().setIgnoreNullValue(true));
} }

View File

@ -47,14 +47,14 @@ public class BusDeviceCommand implements Serializable {
@ApiModelProperty(value = "指令名称") @ApiModelProperty(value = "指令名称")
private String commandName; private String commandName;
@ApiModelProperty(value = "指令类型(1=控制2=查询3=校准") @ApiModelProperty(value = "指令类型(FILE_TRANSFER=文件传输PRINT_CONTROL=打印控制VIDEO_STREAM=视频")
private Integer commandType; private String commandType;
@ApiModelProperty(value = "指令描述") @ApiModelProperty(value = "指令描述")
private String description; private String description;
@ApiModelProperty(value = "是否为系统指令1=是2=否)") @ApiModelProperty(value = "状态1有效0无效")
private Integer isSystem; private Integer status;
@ApiModelProperty(value = "指令参数JSON格式") @ApiModelProperty(value = "指令参数JSON格式")
private String commandParams; private String commandParams;

View File

@ -39,33 +39,21 @@ public class BusUserDevice implements Serializable {
@ApiModelProperty(value = "绑定记录唯一标识") @ApiModelProperty(value = "绑定记录唯一标识")
private Long id; private Long id;
@NotNull
@ApiModelProperty(value = "用户ID外键") @ApiModelProperty(value = "用户ID外键")
private Long userId; private Long userId;
@NotNull
@ApiModelProperty(value = "设备ID外键") @ApiModelProperty(value = "设备ID外键")
private Long deviceId; private Long deviceId;
@NotNull
@ApiModelProperty(value = "是否为主用户1=是2=否)")
private Integer isPrimary;
@NotNull
@ApiModelProperty(value = "绑定状态1=有效2=失效)")
private Integer status;
@ApiModelProperty(value = "创建者") @ApiModelProperty(value = "创建者")
private String createBy; private String createBy;
@ApiModelProperty(value = "更新者") @ApiModelProperty(value = "更新者")
private String updateBy; private String updateBy;
@NotNull
@ApiModelProperty(value = "创建时间") @ApiModelProperty(value = "创建时间")
private Timestamp createTime; private Timestamp createTime;
@NotNull
@ApiModelProperty(value = "更新时间") @ApiModelProperty(value = "更新时间")
private Timestamp updateTime; private Timestamp updateTime;

View File

@ -38,4 +38,7 @@ public interface BusDeviceMapper extends BaseMapper<BusDevice> {
List<BusDevice> findAll(@Param("criteria") BusDeviceQueryCriteria criteria); List<BusDevice> findAll(@Param("criteria") BusDeviceQueryCriteria criteria);
List<BusTreeVo> getDevice(); List<BusTreeVo> getDevice();
void updateDeviceStatus(@Param("deviceSn") String deviceSn, @Param("status") Integer status, @Param("printStatus") Integer printStatus, @Param("errorStatus") Integer errorStatus, @Param("remark") String remark);
} }

View File

@ -18,6 +18,9 @@ package me.zhengjie.modules.system.service;
import java.util.List; import java.util.List;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService;
import me.zhengjie.modules.security.config.enums.CurrentStatusEnum;
import me.zhengjie.modules.security.config.enums.ErrorStatusEnum;
import me.zhengjie.modules.security.config.enums.PrintInfoStatusEnum;
import me.zhengjie.modules.system.domain.BusDevice; import me.zhengjie.modules.system.domain.BusDevice;
import me.zhengjie.modules.system.domain.Menu; import me.zhengjie.modules.system.domain.Menu;
import me.zhengjie.modules.system.domain.dto.BusDeviceQueryCriteria; import me.zhengjie.modules.system.domain.dto.BusDeviceQueryCriteria;
@ -31,6 +34,13 @@ import me.zhengjie.utils.PageResult;
**/ **/
public interface BusDeviceService extends IService<BusDevice> { public interface BusDeviceService extends IService<BusDevice> {
List<BusDevice> getByDeviceSns(List<String> deviceSns);
/**
* 修改设备状态
*/
void updateDeviceStatus(String deviceSn, CurrentStatusEnum e1, PrintInfoStatusEnum e2, ErrorStatusEnum e3, String remarks);
/** /**
* 查询数据分页 * 查询数据分页
* @param criteria 条件 * @param criteria 条件
@ -70,4 +80,6 @@ public interface BusDeviceService extends IService<BusDevice> {
List<BusTreeVo> getDeviceTree(Long pid); List<BusTreeVo> getDeviceTree(Long pid);
void updateOrAdd(BusDevice entity);
} }

View File

@ -15,10 +15,14 @@
*/ */
package me.zhengjie.modules.system.service.impl; package me.zhengjie.modules.system.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import me.zhengjie.exception.BadRequestException; import me.zhengjie.exception.BadRequestException;
import me.zhengjie.modules.front.service.WeChatService; import me.zhengjie.modules.front.service.WeChatService;
import me.zhengjie.modules.security.config.enums.CurrentStatusEnum;
import me.zhengjie.modules.security.config.enums.ErrorStatusEnum;
import me.zhengjie.modules.security.config.enums.PrintInfoStatusEnum;
import me.zhengjie.modules.system.domain.BusDevice; import me.zhengjie.modules.system.domain.BusDevice;
import me.zhengjie.modules.system.domain.Menu; import me.zhengjie.modules.system.domain.BusDeviceCommand;
import me.zhengjie.modules.system.domain.dto.BusDeviceQueryCriteria; import me.zhengjie.modules.system.domain.dto.BusDeviceQueryCriteria;
import me.zhengjie.modules.system.domain.dto.BusTreeVo; import me.zhengjie.modules.system.domain.dto.BusTreeVo;
import me.zhengjie.modules.system.mapper.BusCommandMapper; import me.zhengjie.modules.system.mapper.BusCommandMapper;
@ -29,13 +33,17 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import me.zhengjie.utils.CacheKey; import me.zhengjie.utils.CacheKey;
import me.zhengjie.utils.RedisUtils; import me.zhengjie.utils.RedisUtils;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import me.zhengjie.utils.PageUtil; import me.zhengjie.utils.PageUtil;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Random; import java.util.Random;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import me.zhengjie.utils.PageResult; import me.zhengjie.utils.PageResult;
@ -53,6 +61,45 @@ public class BusDeviceServiceImpl extends ServiceImpl<BusDeviceMapper, BusDevice
private final WeChatService weChatService; private final WeChatService weChatService;
private final RedisUtils redisUtils; private final RedisUtils redisUtils;
@Override
public List<BusDevice> getByDeviceSns(List<String> deviceSns) {
return busDeviceMapper.selectList(new LambdaQueryWrapper<BusDevice>().in(BusDevice::getDeviceSn, deviceSns));
}
@Async
@Override
public void updateDeviceStatus(String deviceSn, CurrentStatusEnum e1, PrintInfoStatusEnum e2, ErrorStatusEnum e3, String remarks) {
busDeviceMapper.updateDeviceStatus(deviceSn, e1.getCode(), e2.getCode(), e3.getCode(), remarks);
}
@Async
@Override
public void updateOrAdd(BusDevice entity) {
List<BusDevice> devices = getByDeviceSns(new ArrayList<String>() {{ add(entity.getDeviceSn()); }});
if (devices.isEmpty()) {
busDeviceMapper.insert(entity);
}
List<BusDeviceCommand> newList = entity.getCommandList();
List<BusDeviceCommand> oldList = busCommandMapper.selectList(new LambdaQueryWrapper<BusDeviceCommand>().eq(BusDeviceCommand::getDeviceId, devices.get(0).getId()));
if (newList.isEmpty() && !oldList.isEmpty()) {
List<Long> dIds = oldList.stream().map(BusDeviceCommand::getId).collect(Collectors.toList());
busCommandMapper.deleteBatchIds(dIds);
} else if (!newList.isEmpty()) {
// 查询第一个列表中 CommandType 在第二个列表中不存在的元素
List<BusDeviceCommand> addList = newList.stream().filter(p1 -> oldList.stream().noneMatch(p2 -> Objects.equals(p1.getCommandType(), p2.getCommandType()))).collect(Collectors.toList());
for (BusDeviceCommand add : addList) {
add.setDeviceId(devices.get(0).getId());
busCommandMapper.insert(add);
}
// 查询第二个列表中 CommandType 在第一个列表中不存在的元素
List<BusDeviceCommand> updList = oldList.stream().filter(p2 -> newList.stream().noneMatch(p1 -> Objects.equals(p2.getCommandType(), p1.getCommandType()))).collect(Collectors.toList());
for (BusDeviceCommand upd : updList) {
upd.setStatus(0); // 修改成指令无效
busCommandMapper.updateById(upd);
}
}
}
@Override @Override
public PageResult<BusDevice> queryAll(BusDeviceQueryCriteria criteria, Page<Object> page){ public PageResult<BusDevice> queryAll(BusDeviceQueryCriteria criteria, Page<Object> page){
return PageUtil.toPage(busDeviceMapper.findAll(criteria, page)); return PageUtil.toPage(busDeviceMapper.findAll(criteria, page));

View File

@ -16,18 +16,14 @@
package me.zhengjie.modules.system.service.webstocket; package me.zhengjie.modules.system.service.webstocket;
import lombok.Data; import lombok.Data;
import me.zhengjie.modules.security.config.enums.MsgType; import me.zhengjie.modules.security.config.enums.MsgEnum;
/**
* @author ZhangHouYing
* @date 2019-08-10 9:55
*/
@Data @Data
public class SocketMsg { public class SocketMsg {
private String msg; private String msg;
private MsgType msgType; private MsgEnum msgType;
public SocketMsg(String msg, MsgType msgType) { public SocketMsg(String msg, MsgEnum msgType) {
this.msg = msg; this.msg = msg;
this.msgType = msgType; this.msgType = msgType;
} }

View File

@ -0,0 +1,43 @@
package me.zhengjie.modules.system.service.webstocket;
import jodd.util.StringUtil;
import lombok.Data;
import me.zhengjie.modules.security.config.enums.MethodEnum;
/**
* 控制请求(小程序->主板): sdcp/request/${MainboardID}
* 控制响应(主板->小程序): sdcp/response/${MainboardID}
* 状态信息(主板->小程序)sdcp/status/${MainboardID}
* 属性信息(主板->小程序)sdcp/attributes/${MainboardID}
* 错误信息(主板->小程序)sdcp/error/${MainboardID}
* 通知信息(主板->小程序)sdcp/notice/${MainboardID}
*/
@Data
public class WebSocketParam {
// 消息topic
private String Topic;
// 设备ID
private String MainboardID;
// 消息类型
private MethodEnum method;
public String getMainboardID() {
if (StringUtil.isBlank(MainboardID)) {
return getTopic().substring(getTopic().lastIndexOf("/") + 1);
}
return MainboardID;
}
public MethodEnum getMethod() {
for (MethodEnum os : MethodEnum.values()) {
if (getTopic().contains(os.name())) {
return os;
}
}
return null;
}
}

View File

@ -1,143 +1,317 @@
/*
* Copyright 2019-2025 Tz
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.zhengjie.modules.system.service.webstocket; package me.zhengjie.modules.system.service.webstocket;
import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import me.zhengjie.modules.security.config.WebSocketConfig;
import me.zhengjie.modules.security.config.enums.*;
import me.zhengjie.modules.system.domain.BusDevice;
import me.zhengjie.modules.system.domain.BusDeviceCommand;
import me.zhengjie.modules.system.domain.dto.BusDeviceQueryCriteria;
import me.zhengjie.modules.system.service.BusDeviceService;
import me.zhengjie.modules.system.service.webstocket.req.WebSocketReqDTO;
import me.zhengjie.modules.system.service.webstocket.req.WebSocketReqData;
import me.zhengjie.modules.system.service.webstocket.res.WebSocketResAttributes;
import me.zhengjie.modules.system.service.webstocket.res.WebSocketResDTO;
import me.zhengjie.modules.system.service.webstocket.res.WebSocketResPrintInfo;
import me.zhengjie.modules.system.service.webstocket.res.WebSocketResStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.websocket.*; import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint; import javax.websocket.server.ServerEndpoint;
import java.io.IOException; import java.io.IOException;
import java.util.Objects; import java.net.URI;
import java.util.concurrent.CopyOnWriteArraySet; import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
@ServerEndpoint("/sdcp/{method}/{mainboardId}") @ServerEndpoint("/webSocket")
@Slf4j @Slf4j
@Component @Component
@Order(2)
public class WebSocketServer { public class WebSocketServer {
/** @Autowired private BusDeviceService deviceService;
* concurrent包的线程安全Set用来存放每个客户端对应的MyWebSocket对象 private static BusDeviceService myDeviceService;
*/
private static final CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<>();
/** // 存储设备
* 与某个客户端的连接会话需要通过它来给客户端发送数据 private static final CopyOnWriteArraySet<String> deviceList = new CopyOnWriteArraySet<>();
*/ // 存储连接的前端客户端
private Session session; private static final ConcurrentHashMap<String, Session> vueClients = new ConcurrentHashMap<>();
/** // 第三方WebSocket连接
* 接收sid @Value("${thirdParty.socket.url}")
*/ private String thirdPartyWsUrl;
private String sid = ""; private static Session thirdPartySession;
private static WebSocketContainer thirdPartyContainer;
// 重连机制
private final ScheduledExecutorService reconnectExecutor = Executors.newSingleThreadScheduledExecutor();
// 初始化连接第三方WebSocket
@PostConstruct
public void initThirdPartyWebSocket() {
if (null == thirdPartyContainer && null == thirdPartySession) { return; }
try {
thirdPartyContainer = ContainerProvider.getWebSocketContainer();
thirdPartySession = thirdPartyContainer.connectToServer(ThirdPartyClient.class, URI.create(thirdPartyWsUrl));
log.info("【客户端->第三方】连接成功");
} catch (Exception e) {
e.printStackTrace();
log.info("【客户端->第三方】连接失败等待5秒重新连接");
scheduleReconnect();
}
if (null == myDeviceService) {
myDeviceService = deviceService;
}
}
// 安排重连任务
private void scheduleReconnect() {
reconnectExecutor.schedule(this::initThirdPartyWebSocket, 5, TimeUnit.SECONDS);
}
/** /**
* 连接建立成功调用的方法 * 连接建立成功调用的方法
*/ */
@OnOpen @OnOpen
public void onOpen(Session session, @PathParam("method") String method, @PathParam("mainboardId") String mainboardId) { public void onOpen(Session session) {
this.session = session; log.info("【VUE->服务端】连接成功sessionId={}", session.getId());
//如果存在就先删除一个防止重复推送消息 vueClients.put(session.getId(), session);
webSocketSet.removeIf(webSocket -> webSocket.sid.equals(mainboardId));
webSocketSet.add(this);
this.sid = mainboardId;
} }
/** /**
* 连接关闭调用的方法 * 连接关闭调用的方法
*/ */
@OnClose @OnClose
public void onClose() { public void onClose(Session session) {
webSocketSet.remove(this); log.info("【VUE->服务端】连接断开sessionId={}", session.getId());
vueClients.remove(session.getId());
}
/**
* 连接异常时
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
log.info("【VUE->服务端】发现异常sessionId={}", session.getId());
error.printStackTrace();
} }
/** /**
* 收到客户端消息后调用的方法 * 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息 * @param message 客户端发送过来的消息
*/ */
@OnMessage @OnMessage
public void onMessage(String message, Session session, @PathParam("method") String method, @PathParam("mainboardId") String mainboardId) { public void onMessage(String message) {
log.info("收到来" + sid + "的信息:" + message); log.info("WebSocketServer onMessage 收到消息={}", message);
if ("ping".equals(message)) { sendMessage(message);
}
/**
* 主推消息给客户端
*/
private void sendMessage(String message) {
// 根据类型操作不同的消息
// todo 如果是指令则转发给第三方WebSocket
if (!(thirdPartySession != null && thirdPartySession.isOpen())) {
log.info("指令发送失败,原因: websocket未连接");
return;
}
try { try {
sendMessage("pong"); thirdPartySession.getBasicRemote().sendText(message);
} catch (IOException e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} log.info("指令发送异常,原因:", e);
} else {
// 群发消息
for (WebSocketServer item : webSocketSet) {
try {
item.sendMessage(message);
} catch (IOException e) {
log.error(e.getMessage(), e);
} }
} }
// 处理来自第三方WebSocket的消息并转发给Vue前端
@ClientEndpoint
public static class ThirdPartyClient {
@OnOpen
public void onOpen(Session session) {
log.info("【第三方客户端】连接成功");
initDevices();
socketMsg(new SocketMsg("已连接", MsgEnum.CONNECT));
} }
@OnMessage
public void onMessage(String message, Session session) {
log.info("【第三方客户端】收到消息:{}", message);
messageReceiveHandle(message);
}
@OnClose
public void onClose(Session session) {
log.info("【第三方客户端】连接断开");
socketMsg(new SocketMsg("连接断开", MsgEnum.DISCONNECT));
} }
@OnError @OnError
public void onError(Session session, Throwable error) { public void onError(Session session, Throwable error) {
log.error("发生错误", error); log.info("【第三方客户端】发现异常,原因:", error);
socketMsg(new SocketMsg("连接异常", MsgEnum.ERROR));
} }
/** // 收到消息解析做响应的处理
* 实现服务器主动推送 private void messageReceiveHandle(String message) {
*/ // 如果是 [心跳] 检测忽略
private void sendMessage(String message) throws IOException { if (WebSocketConfig.PONG.equals(message)) {
this.session.getBasicRemote().sendText(message); log.info("<<< 心跳反馈,忽略");
return;
}
// 其余信息是第三方推送过来的消息 / 发送的指令
message = message.replace("\\", "");
WebSocketParam param = JSON.parseObject(message, WebSocketParam.class);
MethodEnum method = param.getMethod();
String deviceSn = param.getMainboardID();
log.info("<<< 方式 {}", method.name());
// 只关心我已有的设备
if (!deviceList.contains(deviceSn)) {
log.info("<<< 设备【{}】在系统未找到,忽略", deviceSn);
return;
}
// request 说明是vue要发送指令给第三方
if (method == MethodEnum.request) {
WebSocketReqDTO requestReq = JSON.parseObject(message, WebSocketReqDTO.class);
if (null == requestReq.getCommand()) {
log.info("<<< 命令类型未找到");
return;
}
// todo 发送指令给第三方
CapabilitieEnum command = requestReq.getCommand();
// messageSendHandle(null);
}
// 其他说明是第三方上推给我们客服端
else {
WebSocketResDTO resDTO = JSON.parseObject(message, WebSocketResDTO.class);
if (method == MethodEnum.response) {
// List<BusDevice> devices = new ArrayList<>();
// BusDevice device = new BusDevice();
// device.setId();
// devices.add(device);
// pushVueClients();
} else if (method == MethodEnum.status) {
WebSocketResStatus status = resDTO.getStatus();
WebSocketResPrintInfo printInfo = status.getPrintInfo();
myDeviceService.updateDeviceStatus(
deviceSn,
CurrentStatusEnum.getEnumByNum(status.getCurrentStatus()),
PrintInfoStatusEnum.getEnumByNum(printInfo.getStatus()),
ErrorStatusEnum.SDCP_PRINT_ERROR_NONE,
null
);
BusDevice info = new BusDevice();
info.setDeviceSn(deviceSn);
info.setStatus(CurrentStatusEnum.getEnumByNum(status.getCurrentStatus()).getCode());
info.setPrintStatus(PrintInfoStatusEnum.getEnumByNum(printInfo.getStatus()).getCode());
info.setErrorStatus(ErrorStatusEnum.SDCP_PRINT_ERROR_NONE.getCode());
socketMsg(new SocketMsg(JSON.toJSONString(info), MsgEnum.INFO));
} else if (method == MethodEnum.attributes) {
WebSocketResAttributes attributes = resDTO.getAttributes();
String[] capabilities = attributes.getCapabilities();
BusDevice entity = new BusDevice();
entity.setDeviceSn(deviceSn);
entity.setName(attributes.getName());
entity.setModel(attributes.getMachineName());
entity.setFirmwareVersion(attributes.getFirmwareVersion());
entity.setProtocolVersion(attributes.getProtocolVersion());
List<BusDeviceCommand> commandList = new ArrayList<>();
for (String capability : capabilities) {
BusDeviceCommand command = new BusDeviceCommand();
command.setCommandType(capability);
commandList.add(command);
}
entity.setCommandList(commandList);
myDeviceService.updateOrAdd(entity);
} else if (method == MethodEnum.error) {
WebSocketResStatus status = resDTO.getStatus();
WebSocketResPrintInfo printInfo = status.getPrintInfo();
myDeviceService.updateDeviceStatus(
deviceSn,
CurrentStatusEnum.getEnumByNum(status.getCurrentStatus()),
PrintInfoStatusEnum.getEnumByNum(printInfo.getStatus()),
ErrorStatusEnum.SDCP_PRINT_ERROR_NONE,
null
);
BusDevice info = new BusDevice();
info.setDeviceSn(deviceSn);
info.setStatus(CurrentStatusEnum.getEnumByNum(status.getCurrentStatus()).getCode());
info.setPrintStatus(PrintInfoStatusEnum.getEnumByNum(printInfo.getStatus()).getCode());
info.setErrorStatus(ErrorStatusEnum.SDCP_PRINT_ERROR_NONE.getCode());
socketMsg(new SocketMsg(JSON.toJSONString(info), MsgEnum.ERROR));
} else if (method == MethodEnum.notice) {
}
}
} }
/** private void socketMsg(SocketMsg socketMsg) {
* 群发自定义消息
*/
public static void sendInfo(SocketMsg socketMsg, @PathParam("mainboardId") String mainboardId) throws IOException {
String message = JSON.toJSONString(socketMsg); String message = JSON.toJSONString(socketMsg);
log.info("推送消息到" + mainboardId + ",推送内容:" + message); pushVueClients(message);
for (WebSocketServer item : webSocketSet) { }
// 将消息广播给所有前端客户端
private void pushVueClients(String message) {
vueClients.forEach((id, clientSession) -> {
try { try {
//这里可以设定只推送给这个sid的为null则全部推送 clientSession.getBasicRemote().sendText(message);
if (mainboardId == null) { } catch (IOException e) {
item.sendMessage(message); e.printStackTrace();
} else if (item.sid.equals(mainboardId)) {
item.sendMessage(message);
} }
} catch (IOException ignored) { });
} }
// 发送消息给第三方
private void messageSendHandle(String message) {
if (!(thirdPartySession != null && thirdPartySession.isOpen())) {
log.info("<<< 发送消息给第三方-失败 原因: websocket 已关闭");
return;
}
try {
log.info(">>> 发送消息给第三方 {}", message);
thirdPartySession.getBasicRemote().sendText(message);
} catch (IOException e) {
e.printStackTrace();
log.info("<<< 发送消息给第三方-异常 原因:", e);
} }
} }
@Override /**
public boolean equals(Object o) { * 初始化设备并向第三方发指令为了拿到设备的状态
if (this == o) { */
return true; private void initDevices() {
log.info(">>> 设备初始化");
List<BusDevice> devices = myDeviceService.queryAll(new BusDeviceQueryCriteria());
for (BusDevice device : devices) {
String deviceSn = device.getDeviceSn();
long currentTimeLong = System.currentTimeMillis();
WebSocketReqDTO reqDTO = new WebSocketReqDTO();
reqDTO.setTopic(WebSocketConfig.TOPIC_PREFIX + "/" + MethodEnum.request.name() + "/" + deviceSn);
WebSocketReqData data = new WebSocketReqData();
data.setCmd(0);
data.setData(new Object());
data.setRequestID(currentTimeLong + "");
data.setMainboardID(device.getDeviceSn());
data.setTimeStamp(currentTimeLong);
data.setFrom(0);
reqDTO.setData(data);
// 发送 状态刷新消息(Cmd:0) 指令
messageSendHandle(JSON.toJSONString(reqDTO));
// 订阅设备
deviceList.add(deviceSn);
} }
if (o == null || getClass() != o.getClass()) { log.info("<<< 设备初始化");
return false;
} }
WebSocketServer that = (WebSocketServer) o;
return Objects.equals(session, that.session) &&
Objects.equals(sid, that.sid);
}
@Override
public int hashCode() {
return Objects.hash(session, sid);
} }
} }

View File

@ -0,0 +1,30 @@
package me.zhengjie.modules.system.service.webstocket.req;
import lombok.Data;
import me.zhengjie.modules.security.config.enums.CapabilitieEnum;
import me.zhengjie.modules.system.service.webstocket.WebSocketParam;
import java.io.Serializable;
@Data
public class WebSocketReqDTO extends WebSocketParam implements Serializable {
// 消息参数
private WebSocketReqData Data;
// 机器品牌标识32位UUID
private String Id;
// 指令类型
private CapabilitieEnum command;
public CapabilitieEnum getCommand() {
for (CapabilitieEnum os : CapabilitieEnum.values()) {
if (getTopic().contains(os.name())) {
return os;
}
}
return null;
}
}

View File

@ -0,0 +1,27 @@
package me.zhengjie.modules.system.service.webstocket.req;
import lombok.Data;
import java.io.Serializable;
@Data
public class WebSocketReqData implements Serializable {
// 请求命令
private Integer Cmd;
private Object Data;
// 请求ID
private String RequestID;
// 主板ID
private String MainboardID;
// 时间戳
private Long TimeStamp;
// 标识命令来源
private Integer From;
}

View File

@ -0,0 +1,51 @@
package me.zhengjie.modules.system.service.webstocket.res;
import lombok.Data;
import java.io.Serializable;
/**
* "Name": "4KL200URH300.4K", // 机器名称
* "MachineName": "3DPLUS", // 机型名称
* "MainboardID": "12345678", // 主板ID 设备编号 唯一识别码
* "ProtocolVersion": "V3.0.0", // 协议版本暂留
* "FirmwareVersion": "V1.0.0" // 固件版本暂留
* "NumberOfVideoStreamConnected": 1, // 已连接视频流
* "MaximumVideoStreamAllowed": 1, // 最多可连接的视频流
* "Capabilities":[
* "FILE_TRANSFER", // 支持文件传输
* "PRINT_CONTROL", // 支持打印控制
* "VIDEO_STREAM" // 支持视频流传输
* ], // 主板端支持的子协议
* "CameraStatus": 0, // 摄像头接入状态 0断开 1连接
* "RemainingMemory": 123455, // 剩余的文件存储空间大小(bit)
*/
@Data
public class WebSocketResAttributes implements Serializable {
// 机器名称
private String Name;
// 机型名称
private String MachineName;
// 主板ID 设备编号 唯一识别码
private String MainboardID;
// 协议版本暂留
private String ProtocolVersion;
// 固件版本暂留
private String FirmwareVersion;
// 已连接视频流
private Integer NumberOfVideoStreamConnected;
// 最多可连接的视频流
private Integer MaximumVideoStreamAllowed;
// 主板端支持的子协议
private String[] Capabilities;
// FILE_TRANSFER 支持文件传输
// PRINT_CONTROL 支持打印控制
// VIDEO_STREAM 支持视频流传输
// 摄像头接入状态 0断开 1连接
private Integer CameraStatus;
// 剩余的文件存储空间大小(bit)
private Integer RemainingMemory;
}

View File

@ -0,0 +1,23 @@
package me.zhengjie.modules.system.service.webstocket.res;
import lombok.Data;
import me.zhengjie.modules.system.service.webstocket.WebSocketParam;
import java.io.Serializable;
@Data
public class WebSocketResDTO extends WebSocketParam implements Serializable {
// 消息参数
private WebSocketResData Data;
// 状态
private WebSocketResStatus Status;
// 属性
private WebSocketResAttributes Attributes;
// 机器品牌标识32位UUID
private String Id;
}

View File

@ -0,0 +1,21 @@
package me.zhengjie.modules.system.service.webstocket.res;
import lombok.Data;
import java.io.Serializable;
@Data
public class WebSocketResData implements Serializable {
// 请求命令
private Integer Cmd;
private Object Data;
// 请求ID
private String RequestID;
// 时间戳
private Long TimeStamp;
}

View File

@ -0,0 +1,29 @@
package me.zhengjie.modules.system.service.webstocket.res;
import lombok.Data;
import java.io.Serializable;
@Data
public class WebSocketResPrintInfo implements Serializable {
// 打印子状态
private Integer Status;
// 当前打印层数
private Integer CurrentLayer;
// 打印任务总层数
private Integer TotalLayer;
// 当前已打印时间(ms)
private Integer CurrentTicks;
// 总打印时间(ms)
private Integer TotalTicks;
// 打印文件名称
private String Filename;
// 错误码参考后文 ErrorEnum
private Integer ErrorNumber;
// 当前任务数量
private Integer Task;
// 总共任务数量
private Integer Total;
}

View File

@ -0,0 +1,24 @@
package me.zhengjie.modules.system.service.webstocket.res;
import lombok.Data;
import java.io.Serializable;
@Data
public class WebSocketResStatus implements Serializable {
// 当前机器状态部分状态可以共存
private Integer CurrentStatus;
// 上一次机器状态
private Integer PreviousStatus;
// 当前温度 float
private Integer Temp;
// 箱体目标温度 float
private Integer humidity;
private WebSocketResPrintInfo PrintInfo;
}

View File

@ -108,15 +108,17 @@ weChat:
secret: 1b5eca11a1058361b0f14c5933ef6bd6 secret: 1b5eca11a1058361b0f14c5933ef6bd6
# socket # socket
ws: thirdParty:
socket:
url: ws://127.0.0.1:8000/webSocket url: ws://127.0.0.1:8000/webSocket
server:
# 客户端心跳开关 # server:
clientSwitch: false # # 客户端心跳开关
# 间隔时间 单位:秒 # clientSwitch: false
interval: 10 # # 间隔时间 单位:秒
# 最大错误次数 # interval: 10
maxMissCount: 10 # # 最大错误次数
# maxMissCount: 10
# 文件存储路径 # 文件存储路径
file: file:

View File

@ -7,7 +7,7 @@
<result column="command_name" property="commandName"/> <result column="command_name" property="commandName"/>
<result column="command_type" property="commandType"/> <result column="command_type" property="commandType"/>
<result column="description" property="description"/> <result column="description" property="description"/>
<result column="is_system" property="isSystem"/> <result column="status" property="status"/>
<result column="command_params" property="commandParams"/> <result column="command_params" property="commandParams"/>
<result column="create_by" property="createBy"/> <result column="create_by" property="createBy"/>
<result column="update_by" property="updateBy"/> <result column="update_by" property="updateBy"/>
@ -20,7 +20,7 @@
</select> </select>
<sql id="Base_Column_List"> <sql id="Base_Column_List">
id, device_id, command_name, command_type, description, is_system, command_params, create_by, update_by, create_time, update_time id, device_id, command_name, command_type, description, status, command_params, create_by, update_by, create_time, update_time
</sql> </sql>
<select id="findAll" resultMap="BaseResultMap"> <select id="findAll" resultMap="BaseResultMap">

View File

@ -3,15 +3,17 @@
<mapper namespace="me.zhengjie.modules.system.mapper.BusDeviceMapper"> <mapper namespace="me.zhengjie.modules.system.mapper.BusDeviceMapper">
<resultMap id="BaseResultMap" type="me.zhengjie.modules.system.domain.BusDevice"> <resultMap id="BaseResultMap" type="me.zhengjie.modules.system.domain.BusDevice">
<id column="id" property="id"/> <id column="id" property="id"/>
<result column="device_code" property="deviceCode"/>
<result column="device_sn" property="deviceSn"/> <result column="device_sn" property="deviceSn"/>
<result column="model" property="model"/> <result column="model" property="model"/>
<result column="brand" property="brand"/> <result column="brand" property="brand"/>
<result column="name" property="name"/>
<result column="firmware_version" property="firmwareVersion"/> <result column="firmware_version" property="firmwareVersion"/>
<result column="protocol_version" property="protocolVersion"/>
<result column="location" property="location"/> <result column="location" property="location"/>
<result column="status" property="status"/> <result column="status" property="status"/>
<result column="online_time" property="onlineTime"/> <result column="print_status" property="printStatus"/>
<result column="offline_time" property="offlineTime"/> <result column="error_status" property="errorStatus"/>
<result column="total_print_time" property="totalPrintTime"/>
<result column="remark" property="remark"/> <result column="remark" property="remark"/>
<result column="type" property="type"/> <result column="type" property="type"/>
<result column="create_by" property="createBy"/> <result column="create_by" property="createBy"/>
@ -20,6 +22,11 @@
<result column="update_time" property="updateTime"/> <result column="update_time" property="updateTime"/>
</resultMap> </resultMap>
<update id="updateDeviceStatus">
update bus_device set status = #{status}, print_status = #{printStatus}, error_status = #{errorStatus}, remark = #{remark}
<where><if test="deviceSn != null and deviceSn != ''"> device_sn = #{deviceSn} </if></where>
</update>
<select id="getDevice" resultType="me.zhengjie.modules.system.domain.dto.BusTreeVo"> <select id="getDevice" resultType="me.zhengjie.modules.system.domain.dto.BusTreeVo">
select CONCAT('d', id) id, 0 pid, CONCAT('品牌:', brand, ' | ', '型号:', model) name, CONCAT(location, '[', IF(type=1, '3D打印机', '摄像头'), ']') remark, select CONCAT('d', id) id, 0 pid, CONCAT('品牌:', brand, ' | ', '型号:', model) name, CONCAT(location, '[', IF(type=1, '3D打印机', '摄像头'), ']') remark,
(select count(*) from bus_device_command dc where dc.device_id = bus_device.id) subCount (select count(*) from bus_device_command dc where dc.device_id = bus_device.id) subCount
@ -27,7 +34,7 @@
</select> </select>
<sql id="Base_Column_List"> <sql id="Base_Column_List">
id, device_sn, model, brand, firmware_version, location, status, online_time, offline_time, total_print_time, remark, type, create_by, update_by, create_time, update_time id, device_code, device_sn, model, brand, name, firmware_version, protocol_version, location, status, print_status, error_status, remark, type, create_by, update_by, create_time, update_time
</sql> </sql>
<select id="findAll" resultMap="BaseResultMap"> <select id="findAll" resultMap="BaseResultMap">
@ -39,6 +46,7 @@
<if test="criteria.model != null and criteria.model != ''"> and model like concat('%', #{criteria.model}, '%') </if> <if test="criteria.model != null and criteria.model != ''"> and model like concat('%', #{criteria.model}, '%') </if>
<if test="criteria.brand != null and criteria.brand != ''"> and brand like concat('%', #{criteria.brand}, '%') </if> <if test="criteria.brand != null and criteria.brand != ''"> and brand like concat('%', #{criteria.brand}, '%') </if>
<if test="criteria.type != null"> and type = #{criteria.type} </if> <if test="criteria.type != null"> and type = #{criteria.type} </if>
<if test="criteria.name != null"> and name like concat('%', #{criteria.name}, '%') </if>
</where> </where>
order by id desc order by id desc
</select> </select>